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

10

我正在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

如何让多态方法反映对调度函数resolve-it的更改?

4个回答

10

有一种简单的技巧可以重新定义多方法的调度函数。这个想法是将保存调度函数的变量传递给 defmulti,而不是函数本身。注意 defmulti 中的 #'resolve-it,而不仅仅是 resolve-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) ;; "oh-no", expected!!

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

(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"))

1
这个可以,我可以把ns-unmap放在文件的顶部用于实验目的,当我把文件发送给nREPL时,更改会如预期一样生效。 - Kris

2

看起来 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}))

带有结果:

: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)

看起来您可能需要重新初始化REPL以重新定义分派函数。 即使重复所有操作,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)

在这里,我们看到了一个新的会话,它展示了预期的行为:

;; 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 ***

更新

我尝试了Rumid描述的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

Spyx来自哪里? - Kris
1
这是来自tupelo的:https://github.com/cloojure/tupelo#expression-debugging。 - Martin Harrigan

1

另一个小技巧是先将变量定义为 nil:

(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!

请注意,这里存在权衡,如果您重新定义 defmulti,它将删除 defmethods,并且您需要重新定义它们,这就是为什么 defmulti 通常是缓存的,不允许您重新定义它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接