在clojure中使用'绑定'的好例子是什么?

时间:2011-08-24 02:03:55

标签: clojure dynamic-binding

我理解binding形式允许在clojure中重新绑定动态范围。到目前为止,我所看到的唯一用途是用于I / O,例如print,其中*out*会反弹到你当时想要的作家。

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

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

3 个答案:

答案 0 :(得分:8)

我使用绑定有两个原因:

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

    我正在开发一个分布式系统,其中包含几个通过消息交换发送消息进行通信的组件。这些交换有全局名称,我已经定义了这样的名称:

    (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调用包装在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."))))
    

答案 1 :(得分:2)

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

VimClojure的作用如下。它会设置一个binding,其中包含*warn-on-reflection**1*2等所有有趣的Vars。然后它执行命令,然后在一些bookeeping基础设施中将binding中可能已更改的Vars存储起来。

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

答案 2 :(得分:2)

绑定函数在 test 代码中非常有用。这是在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 
         ....))