So, the winner of the first Clojure Macro Challenge (magic partial) is Rich Hickey. Check out the macro defcurried on line 132. I don’t imagine for a moment he read this blog, it’s just that expressing the reducers library without it would have been an exercise in frustration and obfuscating syntax. However, his implementation is a bit special-case; it only produces the curried version and only curries on the last parameter. So I decided to rip off his code and see whether or not I could come up with a more general solution:
(defn- curry
[[params1 params2] body]
(cons (vec params1)
(if (empty? params2)
body
(list (apply list 'fn (vec params2) body)))))
(defn do-curried [symbol to-fn params]
(let [result (split-with (complement vector?) params)
[[name doc meta] [args & body]] result
[doc meta] (if (string? doc) [doc meta] [nil doc])
body (if meta (cons meta body) body)
arity-for-n #(-> % inc (split-at args) (to-fn body))
arities (->>
(range 0 (count args))
(map arity-for-n)
reverse)
before (keep identity [symbol name doc])]
(concat before arities)))
(defmacro defn-curried
"Builds a multiple arity function similar that returns closures
for the missing parameters, similar to ML's behaviour."
[& params]
(do-curried 'defn curry params))
(defmacro fn-curried
"Builds a multiple arity function similar that returns closures
for the missing parameters, similar to ML's behaviour."
[& params]
(do-curried 'fn curry params))
Incidentally, this was written in the LightTable InstaRepl, which is excellent for developing this kind of abstract hack. The one feature it’s missing is proper destructuring support. If you take a look above, you’ll see an identifier “result” that only really exists to allow the InstaRepl to process it more easily.
Anyway, I hope someone finds this useful. I know I will. After using ML/F#, writing functions that return other functions feels like an anti-pattern.