Clojure and me has moved.

Wednesday, May 28, 2008

Multimethods aren't magic (nor mutable)

This post has moved, go to its new location

What does this code display?


(defmulti foo first)
(def print-foo (comp println foo))
(defmethod foo :a [x]
"it's a 'a'!")
(print-foo [:a])

Nothing, it raises an error: it doesn't know what to do with the dispatch value :a. The value of foo is captured when comp is called.

The thing to keep in mind is that defmethod mutates the var, not the multimethod itself — which is of course immutable. The solution is to pass the var instead of the multimethod to comp:


(defmulti foo first)
(def print-foo (comp println #'foo))
(defmethod foo :a [x]
"it's a 'a'!")
(print-foo [:a]) ; prints "it's a 'a'!"

Update or maybe it's a code smell that you should use a macro instead of a function.

Update 2 Clojure's father (Rich Hickey) says : the correct thing to do is use the var.

2 comments:

Rich Hickey said...

You are right - the correct thing to do is use the var - #'foo. This is a specific case of the more general problem of building long-term connections to function objects. Saying (map foo ...) is fine, as foo is unlikely to change (nor would you want to see it change) during the execution of map. But when you 'capture' the value of a var containing a fn (as the call to comp does here), and store it away for use later, you probably want the var itself instead, as it will reflect redefinitions and bug fixes. Macros are not the answer here.

Laurent PETIT said...

This was a long time ago.

New versions of clojure don't have this problem anymore (multimethods can now mutate, in a controlled - synchronized - way)