Reputation: 19948
I am writing a multimethod in the REPL, functions can be redefined just fine, but if I redefine the dispatch function of a multi method it seems not to use the newly refined function:
;; simple fn to resolve defmethod to call, hardcoded to :do-it
(defn resolve-it [] :do-it)
(resolve-it) ;; :do-it, as expected
(defmulti do-something resolve-it)
(defmethod do-something :do-it [] (println "do-it"))
(defmethod do-something :oh-no [] (println "oh-no"))
(do-something) ;; "do-it", as expected
;; now change resolve-it
(defn resolve-it [] :oh-no)
(resolve-it) ;; :oh-no, as expected
(do-something) ;; "do-it", not as expected
(do-something) ;; "do-it", not expected
How do I get the mult imethod to reflect changes to the dispatch function, resolve-it
?
Upvotes: 11
Views: 2995
Reputation: 4816
Another little trick is to def the var to nil first:
(defmulti hello #(do (println "Hello") %))
(defmethod hello :world ([_] (println "World!")))
;> (hello :world)
Hello
World!
(def hello nil) ;; Eval this and you can now redef the defmulti
(defmulti hello #(do (println "Changed!") %))
(defmethod hello :world ([_] (println "World!")))
;> (hello :world)
Changed!
World!
Be warned there's a trade-off, if you redef the defmulti it removes the defmethods and you need to redef those as well, which is why defmulti is normally caching and does not let you redef it.
Upvotes: 1
Reputation: 1707
According to clojuredocs example defmulti
won't allow you to redefine it. You will have to unmap do-something
from the namespace(ns
):
(ns-unmap *ns* 'do-something)
and reassign it, as you did before:
(defmulti do-something resolve-it)
(defmethod do-something :do-it [] (println "do-it"))
(defmethod do-something :oh-no [] (println "oh-no"))
Upvotes: 9
Reputation: 2429
There is a simple technique that allows re-defining the dispatch function of the multimethod. The idea is to pass the var that holds the dispatch function to defmulti
, not the function itself. Note the #'resolve-it
in defmulti
instead of just resolve-it
. Thus the var is de-referenced at runtime, not just at compile time.
(defn resolve-it [] :do-it)
(resolve-it) ;; :do-it, as expected
(defmulti do-something #'resolve-it)
(defmethod do-something :do-it [] (println "do-it"))
(defmethod do-something :oh-no [] (println "oh-no"))
(do-something) ;; "do-it", as expected
;; now change resolve-it
(defn resolve-it [] :oh-no)
(resolve-it) ;; :oh-no, as expected
(do-something) ;; "oh-no", expected!!
Upvotes: 13
Reputation: 29966
It looks like defmulti
is caching the dispatch function. Here is a modified version of your code that illustrates the problem:
;; simple fn to resolve defmethod to call, hardcoded to :do-it
(defn who-is-it [person] (:name person))
(spyx (who-is-it {:name :joe}))
(defmulti do-something who-is-it)
(defmethod do-something :homer [person] :doh)
(defmethod do-something :bill [person] :oh-no)
(defmethod do-something :ted [person] :excellent)
(spyx (do-something {:name :homer}))
(spyx (do-something {:name :bill}))
;; now change who-is-it
(defn who-is-it [arg] :ted)
(spyx (who-is-it :wilma)) ;; expected result = :excellent
(spyx (do-something {:name :betty}))
with results:
:reloading (tst.clj.core)
(who-is-it {:name :joe}) => :joe
(do-something {:name :homer}) => :doh
(do-something {:name :bill}) => :oh-no
(who-is-it :wilma) => :ted
:error-while-loading tst.clj.core
Error refreshing environment: java.lang.IllegalArgumentException: No method in multimethod 'do-something' for dispatch value: :betty, compiling:(tst/clj/core.clj:22:27)
It looks like you may need to reinitialize the REPL to redefine the dispatch fn. Even repeating everything did not overwrite do-something
for me:
(defmulti do-something who-is-it)
(defmethod do-something :homer [person] :doh)
(defmethod do-something :bill [person] :oh-no)
(defmethod do-something :ted [person] :excellent)
(spyx (do-something {:name :betty})) ;=> ***same error ***
Error refreshing environment: java.lang.IllegalArgumentException: No method in multimethod 'do-something' for dispatch value: :betty, compiling:(tst/clj/core.clj:30:1)
Here with a new session we see the expected behavior:
;; simple fn to resolve defmethod to call, hardcoded to :do-it
(defn who-is-it [person] (:name person))
(spyx (who-is-it {:name :joe}))
;; now change who-is-it
(defn who-is-it [arg] :ted)
(spyx (who-is-it :wilma)) ;; expected result = :ted
; (spyx (do-something {:name :betty}))
(defmulti do-something who-is-it)
(defmethod do-something :homer [person] :doh)
(defmethod do-something :bill [person] :oh-no)
(defmethod do-something :ted [person] :excellent)
(dotest
(spyx (do-something {:name :betty})))
(do-something {:name :betty}) => :excellent ; *** as expected ***
I tried the ns-unmap
technique Rumid described and it works also. I noticed you have to reissue both the defmulti
and all of the defmethod
statements:
(ns-unmap *ns* 'do-something) ; be sure to remember the quote
(defmulti do-something who-is-it)
(defmethod do-something :homer [person] :doh)
(defmethod do-something :bill [person] :oh-no)
(defmethod do-something :ted [person] :excellent)
(dotest
(newline)
(spyx (do-something {:name :betty}))) ;=> :excellent
Upvotes: 3