Почему я могу это сделать:
> (set! *unchecked-math* true)
true
> (set! *warn-on-reflection* false)
false
но не могу сделать это:
> (def ^:dynamic *x*)
#'user/*x*
> (set! *x* 1) ;; no luck, exception!
Почему я могу это сделать:
> (set! *unchecked-math* true)
true
> (set! *warn-on-reflection* false)
false
но не могу сделать это:
> (def ^:dynamic *x*)
#'user/*x*
> (set! *x* 1) ;; no luck, exception!
Возможно ли, что встроенная динамика неявно обернута средой выполнения в форму привязки? Потому что это работает, например:
user=> (def ^:dynamic *x*)
user=> (binding [*x* false] (set! *x* true))
true
user=>
Следует отметить, что в документации прямо говорится, что попытка изменить корневую привязку через set!
является ошибкой, см.:
http://clojure.org/reference/vars
Также возможно, что встроенные функции обрабатываются исключительно, например, если вы посмотрите на метаданные x:
user=> (meta #'*x*)
{:dynamic true, :line 1, :column 1, :file "/private/var/folders/8j/ckhdsww161xdwy3cfddjd01d25k_1q/T/form-init5379741350621280680.clj", :name *x*, :ns #object[clojure.lang.Namespace 0x6b8f00 "user"]}
Он помечен как динамический, тогда как *warn-on-reflection*
не помечен как динамический, но по-прежнему работает в форме привязки:
user=> (meta #'*warn-on-reflection*)
{:added "1.0", :ns #object[clojure.lang.Namespace 0x377fc927 "clojure.core"], :name *warn-on-reflection*, :doc "When set to true, the compiler will emit warnings when reflection is\n needed to resolve Java method calls or field accesses.\n\n Defaults to false."}
user=> (binding [*warn-on-reflection* true] (set! *warn-on-reflection* false))
false
user=>
Предположительно, это сделано для обратной совместимости, потому что в более ранних версиях clojure переменные с наушниками (звездочки с каждой стороны) по соглашению были динамическими. Но в любом случае это просто показывает, что встроенные функции обрабатываются немного по-другому.
Теперь я решил пойти дальше, и grep
исходный код clojure ищет warn-on-reflection
, что приводит меня к константе WARN_ON_REFLECTION
, которая приводит меня к таким строкам кода в RT.java
:
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L467
Var.pushThreadBindings(
RT.mapUniqueKeys(CURRENT_NS, CURRENT_NS.deref(),
WARN_ON_REFLECTION, WARN_ON_REFLECTION.deref()
,RT.UNCHECKED_MATH, RT.UNCHECKED_MATH.deref()));
Это заставляет меня поверить, что мое первоначальное предположение было правильным, что некоторые специальные глобальные переменные неявно заключены в локальную привязку потока.
РЕДАКТИРОВАТЬ:
Как упоминалось в комментарии, вы можете использовать clojure.core/push-thread-bindings
, но обязательно следуйте советам документации и завершите try/catch/finally с помощью pop-thread-bindings
в блоке finally. И в этот момент вы бы просто повторно реализовали binding
(например, запустили (source binding)
в repl), вероятно, поэтому документ явно предупреждает, что push-thread-bindings
является функцией низкого уровня, а binding
следует предпочесть.
*warn-on-reflection*
в clojure.core
.
- person OlegTheCat; 10.08.2016
Просматривая документ, вы найдете
user=> (doc thread-bound?)
-------------------------
clojure.core/thread-bound?
([& vars])
Returns true if all of the vars provided
as arguments have thread-local bindings.
Implies that set!'ing the provided vars will succeed.
Returns true if no vars are provided.
особенно:
Подразумевает, что set!, если предоставленные переменные будут успешными
Итак, это означает, что вы можете проверить, возможно ли set!
, следующим образом:
user=> (thread-bound? #'*x*)
false
user=> (thread-bound? #'*unchecked-math*)
true
что означает, что вы можете использовать только set!
привязанные к потоку переменные, а ваш *x*
(пока) не является.
PS: в ответе Kevins вы увидите Var.pushThreadBindings
, который предположительно доступен как clojure.core/push-thread-bindings
- если вы не хотите копать глубже .
Согласно ссылке на переменные, вы можете использовать set!
присваивание только для связанных с потоком переменных:
В настоящее время попытка установить корневую привязку var с помощью set! является ошибкой, т. е. присвоение var является локальным для потока. Во всех случаях возвращается значение expr.
Однако встроенные динамические переменные, такие как *warn-on-reflection*
, имеют локальные привязки потока, поэтому вы можете свободно использовать для них set!
:
(thread-bound? #'*unchecked-math*)
=> true
тогда как (def ^:dynamic *x*)
создает только корневую привязку:
(thread-bound? #'*x*)
=> false
Макрос binding
создает новую область для динамического Var; на низком уровне он временно 'отправляет' связывает значения данных Vars с текущим потоком, а затем 'выталкивает' их при выходе из тела макроса.
(binding [*x* 1]
(thread-bound? #'*x*))
=> true
(binding [*x* 1]
(set! *x* 2))
=> 2