Каковы хорошие примеры использования 'binding' в clojure?

Я понимаю что binding Форма позволяет перепривязывать динамическую область видимости в ближайшем будущем. До сих пор единственное использование, которое я видел, использовалось для ввода-вывода, например с print где *out* отскок к тому, что когда-либо писатель вы хотели бы в то время.

Я хотел бы видеть примеры, которые действительно используют в своих интересах силу binding где другие объекты действительно не работают. Лично я использовал его только в тех случаях, когда передача пользовательского объекта всем функциям была действительно утомительной. В основном это ситуация, когда я пытаюсь создать контекст, который использует вспомогательные функции. (Аналогично этому случаю, когда нужно использовать идиому временного перепривязки-специального-var в Clojure?) Чтобы быть более конкретным, я полагался на пользователя в создании динамической привязки к *db* var, чтобы позволить функциям базы данных знать, над чем работать. Это было особенно полезно, когда пользователю нужно написать множество вложенных вызовов для функций базы данных. Как правило, я в порядке, если мне нужно написать макросы, чтобы мне было проще, но требовать от пользователя этого, кажется, плохо. При этом я стараюсь избегать как можно большего.

Какие еще есть хорошие варианты использования для "связывания", которые я могу скопировать и включить в мой код?

3 ответа

Решение

Я использую привязки по двум причинам:

  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."))))

Функции привязки могут быть очень полезны в тестовом коде. Это одно из больших преимуществ хранения функций в 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 
         ....))

В бэкэнде VimClojure у вас может быть несколько повторений, запущенных в одной и той же JVM. Однако, поскольку соединение между Vim и бэкэндом не является непрерывным, вы потенциально можете получить новый поток для каждой команды. Таким образом, вы не можете легко сохранить состояние между командами.

Что делает VimClojure, так это следующее. Это устанавливает binding со всеми интересными Vars, как *warn-on-reflection*, *1, *2, и так далее. Затем он выполняет команду и затем сохраняет потенциально измененные переменные из binding в некоторой вспомогательной инфраструктуре.

Таким образом, каждая команда просто говорит: "Я принадлежу repl 4711", и она увидит состояние указанного repl. Без влияния на состояние репл 0815.

Другие вопросы по тегам