2011-08-24 30 views
11

我知道binding表单允许在clojure中重新绑定动态范围。到目前为止,我见过的唯一用途是用于I/O,比如print,其中*out*可以反弹到您当时想要的任何作家。在clojure中使用'绑定'有什么好的例子?

我希望看到一些真正利用binding功能的例子,其中的其他设施真的不起作用。就我个人而言,我只在使用用户提供的对象到所有函数时非常乏味的情况下才使用它。基本上是我尝试创建辅助函数使用的上下文的情况。 (类似于这种情况When should one use the temporarily-rebind-a-special-var idiom in Clojure?)更具体地说,我是依靠用户创建一个动态绑定到var的允许数据库函数来知道要操作什么。当用户需要编写大量嵌套的数据库函数调用时,这是特别有用的。通常情况下,如果我需要编写宏来让自己更容易,但是要求用户这样做似乎很糟糕,那么我确定。话虽如此,我尽量避免这么做。

什么是'绑定',我可以复制并纳入我的代码的一些其他良好用例?

+0

给大家,到目前为止都是很好的答案。如果有其他人想要刺伤,我会在选择一个答案之前多留几天。 – bmillare

回答

8

我使用绑定有两个原因:

  1. 使用“全局”的资源,例如数据库连接或消息中介通道运行重写常量或其他符号的其它值测试

testing

我工作在一个分布式系统上有几个组件通过消息交换发送消息进行通信。这些交换机具有全局名称,这是我喜欢这样的定义:

(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] 
    [email protected])) 

我的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.")))) 
+0

“(if(current-connection?)”表达式在最后一个代码块中是否正确?它可以(或者应该是)“if(* amqp-connection *)”而不是“current-connection?” '(true?* amqp-connection *)'或'(some?* amqp-connection *)'? –

+1

Kenny,'current-connection?'只存在'var'* amqp-connection *' 。它的实现可以像'(true?* amqp-connection *)'一样简单,这是我想的代码风格问题,直接使用var没有任何坏处。 – Gert

2

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

VimClojure做了什么,如下。它设置了一个binding,其中包含所有有趣的变量,如*warn-on-reflection**1*2等等。然后它执行命令,然后在一些醒目的基础设施中存储binding中可能发生变化的变量。

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

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]))] 
    [email protected])) 

您如何测试密钥生成器函数?它应该是不可预测的。你可以在任何地方线程(if testing ...)或使用某种模拟框架。或者你可以使用一个“动态嘲讽”随机数发生器的宏,并将这个仅放在测试代码中,让你的生产环境免于被窃。

(deftest test-key-gen 
    (with-fake-prng 
     ....)) 
相关问题