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

Я понимаю, что форма binding позволяет восстанавливать динамическое масштабирование в clojure. Пока что единственное, что я видел, используется для ввода/вывода, например, с print, где *out* отскакивает от того, что когда-либо писатель хотел бы в это время.

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

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

Ответ 1

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

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

тестирование

Я работаю над распределенной системой с несколькими компонентами, которые обмениваются сообщениями, отправляя сообщения по обмену сообщениями. Эти обмены имеют глобальные имена, которые я определил так:

(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), которая бросает описательное исключение, когда вы забыли обернуть свои вызовы 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."))))

Ответ 2

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

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

Итак, каждая команда просто говорит: "Я принадлежу к repl 4711", и он увидит состояние упомянутой реплики. Не влияя на состояние ответа 0815.

Ответ 3

функции привязки могут быть действительно полезны в коде 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]))]
     [email protected]))

Как вы unit test выполняете функцию генератора ключей? он должен быть непредсказуемым. Вы можете повесить (if testing ...) всюду или использовать какую-то насмешливую структуру. или вы можете использовать макрос, который "динамически высмеивает" генератор случайных чисел и помещает этот только в тестовый код, оставляя вашу производственную сторону свободной от крутизны.

(deftest test-key-gen 
   (with-fake-prng 
         ....))