Каковы хорошие примеры использования связывания в clojure? - PullRequest
11 голосов
/ 24 августа 2011

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

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

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

Ответы [ 3 ]

8 голосов
/ 24 августа 2011

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

  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."))))
2 голосов
/ 24 августа 2011

функции привязки могут быть действительно полезны в 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 
         ....))
2 голосов
/ 24 августа 2011

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

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

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

...