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


我明白了 binding form允许在clojure中重新绑定动态范围。到目前为止,我所见过的唯一用途是用于I / O,例如 print 哪里 *out* 是反弹到当时你想要的作家。

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

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


12287
2017-08-24 02:03


起源

对大家来说,到目前为止所有的好答案。在选择答案之前我会留下几天,以防其他人想要刺伤。 - bmillare


答案:


我使用绑定有两个原因:

  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调用包装在一个函数中时抛出一个描述性异常 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."))))

8
2017-08-24 02:30



是个 (if (current-connection?) 表达式在最后一个代码块中是否正确可能(或应该) if (*amqp-connection*) 代替?或者是 current-connection? 基本上 (true? *amqp-connection*) 要么 (some? *amqp-connection*)? - Kenny Evitt
肯尼, current-connection? 只存在以“隐藏”var *amqp-connection* 从视图。它的实现可以很简单 (true? *amqp-connection*) 像你说的。我认为这是代码风格的问题,直接使用var是没有害处的。 - Gert


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

VimClojure的作用如下。它建立了一个 binding 与所有有趣的Vars一样 *warn-on-reflection**1*2, 等等。然后它执行命令,然后将可能更改的Vars存储起来 binding 在一些bookeeping基础设施。

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


2
2017-08-24 06:21





绑定函数可以真正有用 测试 码。这是在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 
         ....))

2
2017-08-24 18:00