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

时间:2017-06-28 15:57:20

标签: 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

如何让mult imethod反映调度函数resolve-it的变化?

3 个答案:

答案 0 :(得分:8)

有一种简单的技术可以重新定义多方法的调度功能。我们的想法是将保存调度函数的var传递给defmulti,而不是函数本身。请注意#'resolve-it中的defmulti,而不只是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!!

答案 1 :(得分:4)

根据clojuredocs exmaple var arr = [ {"123": "valueA"}, {"456": "valueB"} ]; const id = "123"; let value; arr.some(obj => { if (obj[id] || obj[id] === 0) value = obj[id]; }); console.log(value);,我们不允许您重新定义它。您必须从命名空间(defmulti)取消映射do-something

ns

并像以前一样重新分配:

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

答案 2 :(得分:1)

看起来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以重新定义调度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)

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

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