如何在Clojure REPL中重新加载多方法

Kri*_*ris 5 clojure

我在REPL中编写了一个多方法,函数可以很好地重新定义,但是如果我重新定义多方法的调度函数,它似乎不会使用新改进的函数:

;; 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
Run Code Online (Sandbox Code Playgroud)

如何让mult imethod反映调度功能的变化resolve-it

ez1*_*1sl 8

有一种简单的技术允许重新定义多方法的调度功能.我们的想法是传递包含调度函数的var defmulti,而不是函数本身.注意#'resolve-itin defmulti而不是just resolve-it.因此,var在运行时被解除引用,而不仅仅是在编译时.

(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!!
Run Code Online (Sandbox Code Playgroud)


Rum*_*mid 6

根据clojuredocs 示例 defmulti不允许您重新定义它。您必须do-something从命名空间(ns)中取消映射:

(ns-unmap *ns* 'do-something)

并重新分配它,就像您之前所做的那样:

(defmulti do-something resolve-it)
(defmethod do-something :do-it [] (println "do-it"))
(defmethod do-something :oh-no [] (println "oh-no"))
Run Code Online (Sandbox Code Playgroud)


Ala*_*son 3

看起来defmulti正在缓存调度函数。这是代码的修改版本,说明了问题:

;; 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}))
Run Code Online (Sandbox Code Playgroud)

结果:

: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)
Run Code Online (Sandbox Code Playgroud)

看起来您可能需要重新初始化 REPL 来重新定义调度 fn。即使重复一切也没有覆盖do-something我:

(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)
Run Code Online (Sandbox Code Playgroud)

在新会话中,我们看到了预期的行为:

;; 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 ***
Run Code Online (Sandbox Code Playgroud)

更新

我尝试了ns-unmap鲁米德描述的技术,它也有效。我注意到您必须重新发出所有声明:defmulti defmethod

(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
Run Code Online (Sandbox Code Playgroud)