Monkeying with Clojure’s defmethod Macro
I’ve at times1 found the need to use the precise match form of Clojure’s multimethod dispatch value. For example:
(defmulti repeating-mm (juxt first second))
(defmethod repeating-mm [1 2] [_] (str [1 2]))
(repeating-mm (range 1 10))
;=> "[1 2]"
repeating-mm
works as I would expect, but I never liked the need to repeat the [1 2]
dispatch value. Instead, I’ve often wished for a way to create a binding of the dispatch value itself — so I played around with doing just that. My first instinct was to create an anaphora for the dispatch value, bound to $
, ala:
(defmacro defmethod-anaphoric
[multifn dispatch-val & fn-tail]
`(. ~(with-meta multifn {:tag 'clojure.lang.MultiFn})
addMethod
~dispatch-val
(let [~'$ ~dispatch-val]
(fn ~@fn-tail))))
(defmulti a-mm (juxt first second))
(defmethod-anaphoric a-mm [1 2] [_] (str $))
(defmethod-anaphoric a-mm [3 4] [s] (reverse s))
(a-mm [1 2])
;=> "[1 2]"
(a-mm [3 4])
;=> (4 3)
And this works fine. However, using an anaphoric binding is poor form and not idiomatic Clojure code (in general). Therefore, it would be better to provide some sort of named binding for the dispatch value, ala:
(defmacro defmethod-explicit
[multifn dispatch-val & fn-tail]
(let [[kw n & body] fn-tail]
(if (= :as kw)
`(. ~(with-meta multifn {:tag 'clojure.lang.MultiFn})
addMethod
~dispatch-val
(let [~n ~dispatch-val] (fn ~@body)))
`(. ~(with-meta multifn {:tag 'clojure.lang.MultiFn})
addMethod
~dispatch-val
(fn ~@fn-tail)))))
(defmulti expl-mm (juxt first second))
(defmethod-explicit expl-mm [1 2] :as dv [_] (str dv))
(defmethod-explicit expl-mm [3 4] [s] (reverse s))
(expl-mm [1 2])
;=> "[1 2]"
(expl-mm [3 4])
;=> (4 3)
I made the implementation of defmethod-explicit
, uhhhh explicit, to highlight the differences between the code path with the explicit binding of the dispatch value and without. In the case with a binding (i.e. :as dv
), the multimethod is built using a closure over the name dv
. In the case without, there is only a function.
I have a bunch of little language hacks like this laying around and whenever I create one it never ceases to astonish how maleable Clojure (and Lisp in general) can be.
:f
-
This post inspired by Chas Emerick’s post on defrecord default slot values. ↩
4 Comments, Comment or Ping
Scott
Can you explain why the contents of the macro inside the syntax-quote doesn’t look like the defmethod call?
As macros are often compared to templates it seems that the original and the macro would look more similar. I thought perhaps the (. with-meta addMeth etc) was necessary for the let, but I see you’re using it in the form w/o the let.
Aug 10th, 2010
fogus
defmethod-explicit is a modification of the source to defmethod. However, there is no reason why the body of the former couldn’t call the latter directly (in a templated way as you say). :f
Aug 10th, 2010
Jürgen Hötzel
Great post fogus!
Any objections about creating a lexical context around the a standard defmethod expansion (thus hiding defmethod implementation details like in your solution?)
See: http://gist.github.com/518129
Jürgen
Aug 10th, 2010
Paul Hobbs
Awesome job, Fogus. Keep hackin’.
@Jurgen Hotzel Perhaps he wishes his changes to be made part of clojure’s core.
Sep 8th, 2010
Reply to “Monkeying with Clojure’s defmethod Macro”