public
Last active

The -> and ->> macros are enough!

  • Download Gist
threading_macros.clj
Clojure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
;; Let's say you have a map recording the number of siblings for each individual:
 
(def num-sibs {:jack 0, :jill 2, :jane 0, :spot 7})
 
;; Say you want to find the number of only children. We can use ->>
 
(->> num-sibs
(map val)
(filter zero?)
count)
 
;; From the outset, we're treating the map as a sequence. But what if we need
;; to do object-like things to it instead of just sequence-like things. What
;; if I need to add my own entry, say [:me 0]:
 
(->> num-sibs
(assoc :me 0) ; Error!
(map val)
(filter zero?)
count)
 
;; This won't work, because assoc takes the map as its FIRST argument, not
;; its LAST as required by ->> . If we employed -> instead, we'd have a similar
;; problem with map and filter, which both take the sequence argument last. In
;; this particular example, the following works, however:
 
(-> num-sibs
(assoc :me 0)
(->> (map val)
(filter zero?)
count))
 
;; This isn't a very versatile technique, and it's getting messy! This issue
;; has prompted all kinds of complex ideas for threading the result through
;; an arbitrary argument. See
;; http://groups.google.com/group/clojure/browse_thread/thread/5582209f58ef11fb
;; and
;; http://groups.google.com/group/clojure/browse_thread/thread/66ff0b89229be894
;; But we'll see that adding another macro is unnecessary.
;;
;; I just want to write my succession of function calls with a minimum of new
;; abstraction and typing. We've been dismissing an obvious solution, maybe
;; for fear it would add some boilerplate. It's the idea of creating a new
;; function that does the job of assoc, but with rearranged arguments.
;; Thankfully, an annonymous function and Clojure's succinct syntax come to
;; the rescue:
 
(->> num-sibs
(#(assoc % :me 0)) ; Neat, this should have been obvious!
(map val)
(filter zero?)
count)
 
;; That's pretty clean. We've just added (# ... ) around the problematic call,
;; and placed % where the data should flow. Note that, since our closure has
;; just one argument, it works for both -> and ->>. Also, this applies to
;; situations where the data must flow through more than one argument, since
;; % can appear as many times in the clojure code as needed.
;;
;; It's a sure sign of an elegant language when time and again, the most
;; succinct solution requires no new tools!
;;
;; Tyler Perkins, 2011-03-18

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.