Clojure's threading macros
I recently learned Clojure. It was my first experience with a Lisp, and I enjoyed it. It felt like a mix of Ruby, which I use in my day job, and Haskell, which I use in my side projects.
They stood out to me because they’re like a much better version of Flow, one of my Haskell libraries. Since functions with a variable number of arguments are hard in Haskell, Flow only works with functions of a single argument. Clojure’s threading macros can work with any number of arguments in any position.
If you aren’t familiar with them, here is a quick overview of the threading
-> is “thread first”. It is easiest to explain with examples.
(-> x f) ;; (f x) (-> x f g) ;; (g (f x)) (-> x (f y)) ;; (f x y)
It fills in the first argument and carries it through all the forms. If you
want to fill in the last argument, use
->>, the “thread last” macro.
(->> x f) ;; (f x) (->> x f g) ;; (g (f x)) (->> x (f y)) ;; (f y x)
If you need more control than that, you can use
as->, the “thread as” macro.
It allows you to fill in an arbitrary argument.
(as-> x $ (f $)) ;; (f x) (as-> x $ (f $) (g $)) ;; (g (f x)) (as-> x $ (f $ y)) ;; (f x y) (as-> x $ (f y $)) ;; (f y x) (as -> x $ (f $ y) (g z $)) ;; (g z (f x y))
These macros are a powerful way to write expressive functional pipelines. I was surprised to find that they were idiomatic Clojure, considering the backlash Flow got from the Haskell community. That makes me think that Clojurists are more pragmatic than Haskellers.
Regardless, this is a neat feature of Clojure. I wish Haskell had something similar. Unfortunately I think it would require Template Haskell, but it could be fun to write functions like this:
$(as-> x _ (f _ y) (g z _)) -- (g z (f x y))