Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generalized Threading Macro in Clojure

Tags:

macros

clojure

There is now a generalized threading macro in Clojure since 1.5 called as->.

This tweet gives an example of how it works: https://twitter.com/borkdude/status/302881431649128448

(as-> "/tmp" x
      (java.io.File. x)
      (file-seq x)
      (filter (memfn isDirectory) x)
      (count x))

First 'x' is bound to "/tmp" and a file is made out of it. 'x' is rebound again to the resulting file and a put through the 'file-seq' function, etc.


The 'diamond wand' from Swiss Arrows library would do what you're asking for:

(-<> 0
 (* <> 5)
 (vector 1 2 <> 3 4))
; => [1 2 0 3 4]

That said, it isn't something you end up needing often (or ever in my Clojure experience)


In case anyone else comes across this, there is a reason the provided macros exist, but an arbitrary placement one does not: the latter would lead to poor API design.

The -> macro places the argument in the first position. This corresponds to functions that work on some subject argument, e.g., conj, assoc.

The ->> macro places the argument in the last position. This corresponds to functions that work on sequences, e.g., map, reduce.

Design your APIs well, and you'll be less likely to need such a macro.


There was a library that provided this feature, but I forgot where. It might of been in the deprecated clojure-contrib. It was the -$> macro.

But you could derive one from clojure's core -> macro to make the one you're looking for:

(defmacro -$>
    ([x] x)
    ([x form] (if (seq? form)
                (with-meta (map #(if (= %1 '$) x %1) form) (meta form))
                (list form x)))
    ([x form & more] `(-$> (-$> ~x ~form) ~@more)))

And use $ to indicate the insertion point:

user=> (-$> 2 str (identity $) (println $))
2
nil

Technically, you could use multiple $ in one form. But this implementation suffers from expanding the same form multiple times (in exchange for simplicity).