Я заметил, что некоторые библиотеки, такие как использование clojure-Твиттера специальный Вар (те предназначенные для динамического связывания, которые окружаются звездочками) для аутентификации OAuth. Вы сохраняете свою аутентификацию в var и затем используете (myauth с OAuth..). Я думаю, что это - очень хорошее решение этого вида проблемы, потому что можно снова переплести подлинный var для каждого пользователя приложения.
Я следовал подобным маршрутом в почтовом клиенте, который я писал. У меня есть специальный var, названный сессией, которую я связываю с картой с сессией текущего пользователя и информацией о пользователе, и существуют различные важные функции, которые используют информацию от того var. Я записал, что макрос, с сессией для временного повторного переплетения его в контексте ряда форм передал с сессией. Это оказывается довольно чистым решением (мне).
Так, мой вопрос - это: я - 'doin' это обряд'? Действительно ли это - плохое проектное решение или является этими из намеченных использований специального Вара?
Вы кажется, делает это совершенно правильно. Фактически, существует ряд встроенных макросов / contrib, которые работают аналогично, например with-out-str
или clojure.contrib.sql / with-connection
. Последний является довольно важной частью современной инфраструктуры Clojure, поэтому любые идиомы, которые он использует, были тщательно изучены многими людьми.
Важно помнить, что потоки, запускаемые в рамках формы bindings
/ with-bindings
, не наследуют значения отскока. для рассматриваемых варов; скорее они видят корневые привязки. Если вы хотите распространить свои привязки на рабочие потоки / агенты, либо передайте их явно (например, как аргументы функции), либо используйте bound-fn
.
Каждый раз, когда вы создаете глобальную переменную, которую планируете повторно привязать, вы добавляете дополнительный неявный аргумент к каждой функции, которая обращается к этой переменной. В отличие от правильных (явных) аргументов, этот скрытый аргумент не отображается в сигнатуре функции, и может быть мало признаков того, что функция его использует. Ваш код стал менее «функциональным»; вызов одной и той же функции с одинаковыми аргументами может привести к разным возвращаемым значениям в зависимости от текущего состояния этих глобальных динамических переменных.
Преимущество глобальных переменных заключается в том, что вы можете легко указать значение по умолчанию, и это позволяет вам лениться, поскольку вам не нужно передавать эту переменную каждой функции, которая ее использует.
Обратной стороной является то, что ваш код сложнее читать, тестировать, использовать и отлаживать. И ваш код становится потенциально более подверженным ошибкам; легко забыть связать или повторно связать переменную перед вызовом функции, которая ее использует, но не так просто забыть передать параметр сеанса
, когда он находится прямо в списке аргументов.
Таким образом, вы получаете загадочные ошибки и странные неявные зависимости между функциями. Рассмотрим следующий сценарий:
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 в качестве явного аргумента каждой функции, которая хочет что-либо напечатать.)
Связующие функции отлично подходят для тестового кода.
Я широко использую функции-обертки перепривязки в своем тестовом коде для таких вещей, как макетирование генератора случайных чисел, использование фиксированного размера блока и т.д... чтобы я мог действительно проверить функцию шифрования на известном выходе.
(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, который можно использовать, чтобы не потерять привязку при использовании потоков.