prog1 in Clojure

Clean your code with this one weird macro trick.

When programming Clojure, sometimes you need the equivalent of Common Lisp’s prog1 macro. It’s like Clojure’s do except that it returns the value of the first form, not the last one.

You could depend on useful, which calls it returning:

(defmacro returning
  "Compute a return value, then execute other forms for side effects.
  Like prog1 in common lisp, or a (do) that returns the first form."
  [value & forms]
  `(let [value# ~value]
     ~@forms
     value#))

Of course, when you’re in a hurry, there’s no time for adding new dependencies. There’s not even time to write your own inline version of the macro. Besides, they say that you shouldn’t ever write your own macros. So what do you do? You compose doto and do:

(doto x
  (do
    (y)
    (z)))
;; returns x

Update (September 2021): Robert Levy pointed out to me that you can use constantly. Clever!

((constantly x)
 (y)
 (z))

But maybe instead of x you have (a complicated form) and you want to give its result a name. Luckily there’s as->.

(doto (a complicated form)
  (as-> x 
    (do
      (y x)
      (z x))))

When you’re debugging a long -> thread, a print function that returns the printed value would be handy so you could insert it in the middle of the chain. This is of course exactly what tools.trace is for. But again, who has time for adding dependencies? Just use doto.

(-> thing
    do-something-1
    do-something-2
    (doto prn)
    do-something-3)

By the way, the other day I wrote this macro that resembles cond->:

(defmacro if-> [expr cond then else]
  `(let [e# ~expr]
     (if ~cond (-> e# ~then) (-> e# ~else))))

I used it with a builder object. The code looked something like this:

(-> (Builder.)
    (.withSetting "password" "kissa2")
    (if-> production-mode?
      (.useProductionMode)
      (.useTestingMode))
    (.build))

Can somebody figure out how to do that nicely without writing a macro?


Comments or questions? Send me an e-mail.


Want to get these articles to your inbox? Subscribe to the newsletter: