我理解binding
形式允许在clojure中可重新绑定的动态作用域。到目前为止,我看到它的唯一用途是用于I/O,例如print
,其中*out*
被反弹到您当时想要的任何写入器。
我想看到真正利用binding
的力量的例子,而其他设施确实不起作用。就我个人而言,我只在将用户提供的对象传递给所有函数非常繁琐的情况下使用它。基本上是我试图创建辅助函数使用的上下文的情况。(类似于这种情况,什么时候应该在Clojure中使用临时重新绑定一个特殊变量的习惯用法?)更具体地说,我依赖于用户创建到*db*
变量的动态绑定,以允许数据库函数知道要操作什么。当用户需要编写大量对数据库函数的嵌套调用时,这一点特别有用。通常情况下,如果我需要编写宏来简化自己的工作,我可以接受,但要求用户这样做似乎很糟糕。话虽如此,我还是尽量避免这样做。
还有什么好的"绑定"用例,我可以复制并合并到我的代码中?
我使用绑定有两个原因:
- 运行覆盖常量或其他符号的其他值的测试
- 使用"全局"资源,如数据库连接或消息代理通道
我正在研究一个分布式系统,其中有几个组件通过消息交换发送消息进行通信。这些交换具有全局名称,我将其定义为:
(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)
函数,当你忘记在with-connection
中包装你的RabbitMQ调用时,它会抛出一个描述性异常:
(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."))))
在VimClojure后端中,您可能在同一个JVM中运行多个repls。但是,由于Vim和后端之间的连接不是连续的,因此您可能会为每个命令获得一个新线程。因此,您不能轻易地在命令之间保留状态。
VimClojure的作用如下。它设置了一个binding
,其中包含所有有趣的变量,如*warn-on-reflection*
、*1
、*2
等。然后它执行命令,然后将binding
中可能更改的var存储在某些bookeeping基础结构中。
所以每个命令都说"我属于repl 4711",它会看到所述repl的状态。不影响repl 0815的状态
绑定函数在测试代码中非常有用。这是将函数存储在变量中的一大优点(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 ...)
或使用某种mock框架。或者,您可以使用一个"动态模拟"随机数生成器的宏,并将这个仅放在测试代码中,从而使您的生产端免受影响。
(deftest test-key-gen
(with-fake-prng
....))