Когда нужно использовать temporarily-rebind-a-special-var идиому в Clojure?

Я заметил, что некоторые библиотеки, такие как использование clojure-Твиттера специальный Вар (те предназначенные для динамического связывания, которые окружаются звездочками) для аутентификации OAuth. Вы сохраняете свою аутентификацию в var и затем используете (myauth с OAuth..). Я думаю, что это - очень хорошее решение этого вида проблемы, потому что можно снова переплести подлинный var для каждого пользователя приложения.

Я следовал подобным маршрутом в почтовом клиенте, который я писал. У меня есть специальный var, названный сессией, которую я связываю с картой с сессией текущего пользователя и информацией о пользователе, и существуют различные важные функции, которые используют информацию от того var. Я записал, что макрос, с сессией для временного повторного переплетения его в контексте ряда форм передал с сессией. Это оказывается довольно чистым решением (мне).

Так, мой вопрос - это: я - 'doin' это обряд'? Действительно ли это - плохое проектное решение или является этими из намеченных использований специального Вара?

5
задан Rayne 15 February 2010 в 03:08
поделиться

3 ответа

Вы кажется, делает это совершенно правильно. Фактически, существует ряд встроенных макросов / contrib, которые работают аналогично, например with-out-str или clojure.contrib.sql / with-connection . Последний является довольно важной частью современной инфраструктуры Clojure, поэтому любые идиомы, которые он использует, были тщательно изучены многими людьми.

Важно помнить, что потоки, запускаемые в рамках формы bindings / with-bindings , не наследуют значения отскока. для рассматриваемых варов; скорее они видят корневые привязки. Если вы хотите распространить свои привязки на рабочие потоки / агенты, либо передайте их явно (например, как аргументы функции), либо используйте bound-fn .

7
ответ дан 13 December 2019 в 19:26
поделиться

Каждый раз, когда вы создаете глобальную переменную, которую планируете повторно привязать, вы добавляете дополнительный неявный аргумент к каждой функции, которая обращается к этой переменной. В отличие от правильных (явных) аргументов, этот скрытый аргумент не отображается в сигнатуре функции, и может быть мало признаков того, что функция его использует. Ваш код стал менее «функциональным»; вызов одной и той же функции с одинаковыми аргументами может привести к разным возвращаемым значениям в зависимости от текущего состояния этих глобальных динамических переменных.

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

Обратной стороной является то, что ваш код сложнее читать, тестировать, использовать и отлаживать. И ваш код становится потенциально более подверженным ошибкам; легко забыть связать или повторно связать переменную перед вызовом функции, которая ее использует, но не так просто забыть передать параметр сеанса , когда он находится прямо в списке аргументов.

Таким образом, вы получаете загадочные ошибки и странные неявные зависимости между функциями. Рассмотрим следующий сценарий:

user> (defn foo [] (when-not (:logged-in *session*) (throw (Exception. "Access denied!"))))
#'user/foo
user> (defn bar [] (foo))
#'user/bar
user> (defn quux [] (bar))
#'user/quux
user> (quux)
; Evaluation aborted.  ;; Access denied!

Поведение quux неявно зависит от сеанса, имеющего значение, но вы не узнаете этого, если не копаетесь во всех вызовах функции quux , и каждая функция, которую вызывают эти функции. Представьте цепочку вызовов глубиной 10 или 20 уровней с одной функцией внизу в зависимости от * сеанса * . Удачи отладки.

Если бы вместо этого у вас было (defn foo [session] ...) , (defn bar [session] ...) , (defn quux [session] ...) , вам сразу станет очевидно, что если вы вызовете quux , вам лучше подготовить сессию.

Лично я бы использовал явные аргументы, если бы у меня не было сильного, разумного значения по умолчанию, которое использовалось бы множеством функций и которое я планировал очень редко или никогда не повторял. (например, было бы глупо передавать STDOUT в качестве явного аргумента каждой функции, которая хочет что-либо напечатать.)

3
ответ дан 13 December 2019 в 19:26
поделиться

Связующие функции отлично подходят для тестового кода.

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

(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]))
             com.cryptovide.modmath/mody 719
             com.cryptovide.modmath/field-size 10]
        ~@exprs))

(is (= (with-fake-prng (encrypt-string "asdf")) [23 54 13 63]))  

При использовании привязок полезно помнить, что они перепривязываются только к текущему потоку, поэтому, когда вы запускаете что-то в pmap, использующее пул потоков, вы можете потерять свои привязки. Если у вас есть код, который параллельно строит строку следующим образом:

(with-out-str
    (pmap process-data input))

Использование этого невинного цикла \p перед картой приведет к тому, что привязка исчезнет, потому что функция process-data будет выполняться в нескольких потоках из пула потоков.

EDIT: Michał Marczyk указывает на макрос bound-fn, который можно использовать, чтобы не потерять привязку при использовании потоков.

1
ответ дан 13 December 2019 в 19:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: