在使用Slime repl在Emacs中进行开发时,我无法重新加载多方法。
重新定义defmethod
表单可以正常工作,但如果我更改了调度函数,我似乎无法重新加载defmulti
表单。我想我专门添加或删除了调度函数参数。
作为一种解决方法,我已经能够ns-unmap
多方法var,重新加载defmulti
表单,然后重新加载所有defmethod
表单。
据推测,这是Clojure实现多方法的一种“限制”,即我们牺牲了一些执行速度的动力,但有没有任何成语或开发实践可以帮助解决这个问题?
答案 0 :(得分:8)
简短的回答是,你处理这个问题的方式是完全正确的。如果您发现自己更新多方法以便特别频繁地更改调度功能,(1)我认为这很不寻常:-),(2)您可以编写一套函数/宏来帮助重新加载。我绘制了两个未经测试的(!)宏来帮助(2)进一步帮助。
然而,首先,简要讨论“为什么”。当前实现的多方法的调度函数查找不需要同步 - 调度fn存储在final
对象的MultiFn
字段中。这当然意味着你不能只改变给定多方法的调度函数 - 你必须重新创建多方法本身。正如您所指出的那样,必须重新注册所有先前定义的方法,这是一件麻烦事。
当前行为允许您重新加载包含defmethod
表单的命名空间,而不会丢失所有方法,代价是当您确实想要这样做时,更换实际的多方法会稍微麻烦一些。
如果您真的想要,可以通过反射更改调度fn,但这会产生有问题的语义,特别是在多线程场景中(有关final
字段反馈更新的信息,请参阅Java Language Specification 17.5.3构造)。
(2)的一种方法是在重新定义之后使用宏(未经测试的)
自动重新添加方法(defmacro redefmulti [multifn & defmulti-tail]
`(let [mt# (methods ~multifn)]
(ns-unmap (.ns (var ~multifn)) '~multifn)
(defmulti ~multifn ~@defmulti-tail)
(doseq [[dispval# meth#] mt#]
(.addMethod ~multifn dispval# meth#))))
另一种设计方法是使用一个叫做with-method-reregistration
的宏,取一个可选的multifn名称和一个正文,并承诺在执行正文后重新注册这些方法;这是一个草图(再次,未经测试):
(defmacro with-method-reregistration [multifns & body]
`(let [mts# (doall (zipmap ~(map (partial list 'var) multifns)
(map methods ~multifns))))]
~@body
(doseq [[v# mt#] mts#
[dispval# meth#] mt#]
(.addMethod @v# dispval# meth#))))
你用它来说(with-method-reregistration [my-multi-1 my-multi-2] (require :reload 'ns1 ns2))
。不确定这是否值得失去清晰度。