有没有办法在定义后使函数var动态化?

时间:2013-12-06 04:26:18

标签: function dynamic clojure

发布更新以使其与事件过程相关(响应并消除混乱)。

非常感谢你的时间和帮助!

在Clojure的某个先前版本中,每个var都可以用“绑定”形式绑定。 如果没有定义为动态的话,你现在得到“无法动态绑定非动态变量”。

在某些情况下,在定义之后使函数var动态变得有用(在测试中进行存根/模拟)。

不要尝试:

(def ^{:dynamic true} log-call #'log-call)

它最终将导致StackOverflowError,因为您正在定义一个自我调用的函数(感谢您的解释)。

更新的问题:

建议的方法似乎不起作用。

从绑定表单调用的表单不​​会定义绑定。

你能帮我找出我错过的东西吗?

以下是更新后的代码:

(def all-expenses [{:amount 33.0 :date "today"}
                   {:amount 44.0 :date "yesterday"}])

(defn fetch-all-expenses [])

(defn fetch-expenses-greater-than [threshold]
  (let [all (fetch-all-expenses)]
    ;calling from a nested form does not see the dynamically bound definition!
    (println "2) from fetch-expenses-greater-than:" (fetch-all-expenses))
    all))

(defn wrap [f]
  (fn [& args] (apply f args)))

(def ^{:dynamic true} fetch-all-expenses (wrap fetch-all-expenses))

(binding [fetch-all-expenses (constantly all-expenses)]
  (let [filtered (fetch-expenses-greater-than 15.0)]
    (println "1) from inside binding:" (fetch-all-expenses))));calling from binding form OK!

在repl中执行的结果是:

2) from fetch-expenses-greater-than: nil
1) from inside binding: [{:date today, :amount 33.0} {:date yesterday, :amount 44.0}]
nil

如果我将fetch-all-expenses的定义更改为

(defn ^:dynamic fetch-all-expenses [])

结果如预期:

2) from fetch-expenses-greater-than: [{:date today, :amount 33.0} {:date yesterday, :amount 44.0}]
1) from inside binding: [{:date today, :amount 33.0} {:date yesterday, :amount 44.0}]
nil

3 个答案:

答案 0 :(得分:1)

(def ^{:dynamic true} log-call #'log-call)此语句说:“创建一个var log-call并将其绑定到var log-call。因此,当您尝试使用log-call var时,它将继续引用自身永远,因此StackOverflow例外。

您可以尝试这样的事情:

(defn wrap [f]
  (fn [& args] (apply f args)))

(def ^{:dynamic true} log-call (wrap log-call))

(def ^{:dynamic true} fetch-all-expenses (wrap fetch-all-expenses))

答案 1 :(得分:1)

可以在定义之后创建一个Var动态,但这对在此更改之前编译的代码没有影响(它仍将使用根绑定)。使用with-redefs在测试期间为函数安装自定义替换。

这样做的原因是Var是否标记为动态确定了编译使用此Var的代码的方式。如果它动态,这样的代码将直接获得根绑定,节省一些工作;如果它是动态的,它将经历一个更复杂的过程,检查是否有适合它的线程局部绑定。

因此,在标记Var保持函数动态之后,无法使已编译的代码使用与binding一起安装的自定义函数。但是,这些调用仍然通过Var,它们恰好直接进入根绑定,因此如果将它们安装为适当Vars的根绑定,则可以使用自定义替换函数进行测试等。 with-redefs封装了干净利落的所有必要逻辑。

让我们看看它在REPL中是如何工作的:

;; define a non-dynamic Var:
(def foo 0)

;; this will throw an exception complaining about the attempt
;; to bind a non-dynamic Var:
(binding [foo 1]
  foo)

;; let's define a function using foo;
;; we'll use it further down:
(defn bar []
  foo)

;; now let's mark the Var dynamic:
(.setDynamic #'foo)

;; this will now evaluate to 1:
(binding [foo 1]
  foo)

;; however, this will still return 0:
(binding [foo 1]
  (bar))

答案 2 :(得分:0)

非常感谢你的回答@MichałMarczyk。这解释了它。

在使代码变为动态之前使用var:

(def foo 0)
(defn bar []
  foo)
(.setDynamic #'foo)
(binding [foo 1]
     ;; prints foo 1 . bar 0
     (println  "foo" foo ". bar" (bar)))

使用var后的代码使其动态化:

(def foo 0)
(.setDynamic #'foo)
(defn bar []
  foo)
(binding [foo 1]
     ;; prints foo 1 . bar 1
     (println  "foo" foo ". bar" (bar)))

是的!....使用with-redefs代替binding一切都按预期工作。这正是我所需要的。