12

我知道该binding表单允许在 clojure 中重新绑定动态范围。到目前为止,我看到它的唯一用途是用于 I/O,例如printwhere*out*可以反弹到您当时想要的任何作家。

我希望看到真正利用binding其他设施真正不起作用的地方的例子。就我个人而言,我只在将用户提供的对象传递给所有函数非常乏味的情况下使用它。基本上是我试图创建辅助函数使用的上下文的情况。(类似于这种情况,什么时候应该在 Clojure 中使用临时重新绑定-a-special-var 成语?)更具体地说,我依赖于用户创建一个动态绑定到*db*var 让数据库函数知道要操作什么。当用户需要编写大量对数据库函数的嵌套调用时,这特别有用。通常,如果我需要编写宏来让自己更轻松,我可以,但要求用户这样做似乎很糟糕。话虽如此,我尽量避免这样做。

我可以复制并合并到我的代码中的“绑定”还有哪些其他好的用例?

4

3 回答 3

8

我使用绑定有两个原因:

  1. 运行覆盖常量或其他符号的其他值的测试
  2. 使用“全局”资源,例如数据库连接或消息代理通道

测试

我正在开发一个分布式系统,该系统具有多个组件,这些组件通过消息交换发送消息来进行通信。这些交易所具有全局名称,我将其定义如下:

(ns const)
(def JOB-EXCHANGE    "my.job.xchg")
(def CRUNCH-EXCHANGE "my.crunch.xchg")
;; ... more constants

这些常量在许多地方被用来将消息发送到正确的地方。为了测试我的代码,我的部分测试套件运行使用实际消息交换的代码。但是,我不希望我的测试干扰实际系统。

为了解决这个问题,我将我的测试代码包装在一个binding覆盖这些常量的调用中:

;; in my testing code:
(binding [const/CRUNCH-EXCHANGE (str const/CRUNCH-EXCHANGE (gensym "-TEST-"))
          const/CRUNCH-TASK-QUEUE (str const/CRUNCH-TASK-QUEUE (gensym "-TEST-"))]
  ;; tests here
)

在这个binding函数内部,我可以调用任何使用常量的代码,它会使用被覆盖的值。

使用全球资源

我使用绑定的另一种方法是“修复”特定范围内的全局或单例资源的值。这是我编写的 RabbitMQ 库的示例,其中我将 RabbitMQ 的值绑定Connection到符号*amqp-connection*,以便我的代码可以使用它:

(with-connection (make-connection opts)
  ;; code that uses a RabbitMQ connection
)

的实现with-connection非常简单:

(def ^{:dynamic true} *amqp-connection* nil)

(defmacro with-connection
  "Binds connection to a value you can retrieve
   with (current-connection) within body."
  [conn & body]
  `(binding [*amqp-connection* ~conn]
     ~@body))

我的 RabbitMQ 库中的任何代码都可以使用该连接*amqp-connection*并假定它是有效的、打开的Connection. 或者使用该(current-connection)函数,当您忘记将 RabbitMQ 调用包装在 a 中时,该函数会引发描述性异常with-connection

(defn current-connection
  "If used within (with-connection conn ...),
   returns the currently bound connection."
  []
  (if (current-connection?)
    *amqp-connection*
    (throw (RuntimeException.
      "No current connection. Use (with-connection conn ...) to bind a connection."))))
于 2011-08-24T02:30:38.953 回答
2

在 VimClojure 后端,你可能有多个 repl 在同一个 JVM 中运行。然而,由于 Vim 和后端之间的连接不是连续的,你可能会为每个命令获得一个新线程。所以你不能轻易地保留命令之间的状态。

VimClojure 的作用如下。binding它使用所有有趣的 Var设置 a ,如*warn-on-reflection**1*2等。然后它执行命令,然后将可能更改的变量存储binding在一些簿记基础设施中。

所以每个命令都只是说“我属于 repl 4711”,它会看到所说的 repl 的状态。不影响 repl 0815 的状态。

于 2011-08-24T06:21:12.533 回答
2

绑定函数在测试代码中非常有用。这是将函数存储在 vars 中的一大优势(正如 Clojure 默认情况下所做的那样)。

我写的一个密码学程序的摘录。

(defmacro with-fake-prng [ & exprs ]
  "replaces the prng with one that produces consisten results"
  `(binding [com.cryptovide.split/get-prng (fn [] (cycle [1 2 3]))]
     ~@exprs))

您如何对密钥生成器功能进行单元测试?它应该是不可预测的。您可以在任何地方使用线程(if testing ...)或使用某种模拟框架。或者您可以使用一个“动态模拟”随机数生成器的宏,并将其仅放在测试代码中,让您的生产端免于繁琐。

(deftest test-key-gen 
   (with-fake-prng 
         ....))
于 2011-08-24T18:00:32.317 回答