【问题标题】:Is there a way to make a function var dynamic after definition?有没有办法在定义后使函数 var 动态化?
【发布时间】:2013-12-06 04:26:17
【问题描述】:

更新帖子以使其与事件进程相关(响应和消除混乱)。

非常感谢您的宝贵时间和帮助!

在某些先前版本的 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)我的代码有什么问题?我的行(def ^{:dynamic true} log-call #'log-call) 产生(meta #'log-call) {:ns #<Namespace user>, :name log-call, :dynamic true, :column 1, :line 19, :file "NO_SOURCE_PATH"} 并且与定义函数时仅添加元数据不同。 (defn ^:dynamic log-call [id] (println "Audit - called" id)) 产生 {:arglists ([id]), :ns #<Namespace user>, :name log-call, :dynamic true, :column 1, :line 4, :file "NO_SOURCE_PATH"} 所以,我的方法(问题 1)有缺陷或不完整。

标签: function dynamic clojure


【解决方案1】:

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

这样做的原因是 Var 是否标记为动态决定了使用此 Var 的代码的编译方式。如果是动态的,这样的代码只会直接获取根绑定,省了一些工作;如果它是动态的,它会经历一个更复杂的过程来检查是否存在线程本地绑定。

因此,在将保持函数的 Var 标记为动态后,无法使已编译的代码使用使用 binding 安装的自定义函数。但是,这些调用仍然通过 Var,它们只是碰巧直接转到根绑定,因此如果您将它们安装为适当 Var 的根绑定,则可以使用自定义替换函数进行测试等。 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))

【讨论】:

  • 哦,(with-redefs [foo 1] (bar)) 的计算结果为 1。
【解决方案2】:

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

【讨论】:

  • 我认为wrap这里是partial的重新定义。
  • 但这是一个部分重新定义;-)
  • 你看,我已经重构了问题和代码以阐明事件的当前状态。非常感谢您的帮助,我非常感谢您对此事的任何了解。
【解决方案3】:

非常感谢您@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 一切都按预期工作。这正是我所需要的。

【讨论】:

    猜你喜欢
    • 2023-03-21
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多