Clojure中的动态函数绑定运行时实现

5

今天我开始尝试使用Clojure,并偶然发现可以在运行时动态更改函数。这听起来非常酷,所以我写了一小段代码来使用这个功能。

(defn ^:dynamic state [x]
   (odd x))

(defn even [x]
  (if (= x 0)
    (println "even")
    (binding [state odd] (parity x))))

(defn odd [x]
  (if (= x 0)
    (println "odd")
    (binding [state even](parity x))))

(defn parity [x]
    (state (dec x)))

它可以正常运行,但由于我完全不了解Clojure,所以不知道这是否是: a) 清洁的函数式代码(因为奇数和偶数似乎具有副作用?) b) 在运行时更改功能的正确方法
我会很感激任何形式的建议! :) -Zakum
2个回答

5
动态绑定的使用在很大程度上是一种偏好问题,但有一些考虑因素:
动态绑定基本上是显式地在调用堆栈上传递值的快捷方式。只有少数情况下这样做才是完全明显的优势;主要是像通过不支持它们的API将“全局”配置设置/参数“通过”传递的情况。
依赖于动态绑定的API很难封装成更明确的东西,而反过来则容易得多(通常可以半自动完成)。
动态绑定与惰性序列或任何在当前调用堆栈之外计算的其他内容(例如其他线程)不兼容。
总的来说,我认为“更干净”的功能解决方案是将state作为参数传递给parity,但是可以从两个方面进行论证。

谢谢你的见解,Joost!所以我理解得没错,我的做法并不是亵渎吗? 我知道,这个功能可以用不同的方式来编写。但是,我至少正确地捕捉了Clojure中动态绑定的思想吗? - Zakum
个人而言,我会尝试使用显式传递值而不是动态绑定,主要是因为我提到的绑定的缺点,但它可以使代码更清晰、更易于理解。 - Joost Diepenmaat

3

虽然可以动态地将符号绑定到不同的函数,但我猜你真正想要的是重新定义一个函数。

这样想吧:你的代码创建了一个符号和两个函数,并且你动态地将符号绑定到不同的函数:

                                   +---> func1
                                  /
symbol ---- [dynamic binding] ---<
                                  \
                                   +---> func2

您的动态绑定效果仅限于binding调用范围内。
我们想要实现的是,给定一个符号和一个函数,为该函数提供新的实现,以便所有引用它的代码都将访问新的实现。
(defn func1 [...])

(var func1) ; ---> func1

(defn func1 [...])

(var func1) ; ---> func1*

这样的更改会永久性地影响所有使用 func1 的代码。在开发 Clojure 代码时,这是一个常见任务:您很可能已经打开了运行应用程序的 REPL,并且您将一遍又一遍地 defdefn 相同的符号,随时重新定义应用程序的所有组件。

如果您正在使用 Emacs 和 SLIME/Swank,则每次在修改过的 Clojure 源文件上按下 C-c C-k,您都有可能重新定义名称空间中的所有函数,而无需重新启动应用程序。


我并不认为那是我的意思(虽然我不确定^^)。你告诉我的是,我可以编写一个函数,使用它,重新定义它,再次使用它,等等,对吗?对我来说,这是静态的,因为更改是由程序员手动进行的。我有兴趣编写在运行时更改其他函数的函数。一个更实际的例子是向现有函数添加日志记录。 一个更奇特的例子是改变自身的函数。 - Zakum
据我所知,Clojure 中的动态变量操作的是 Var 绑定而不是符号。 - siefca

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