Почему я могу установить! встроенные динамические (?) Clojure вары?

Почему я могу это сделать:

> (set! *unchecked-math* true)
true
> (set! *warn-on-reflection* false)
false

но не могу сделать это:

> (def ^:dynamic *x*)
#'user/*x*
> (set! *x* 1) ;; no luck, exception!

person OlegTheCat    schedule 10.08.2016    source источник


Ответы (3)


Возможно ли, что встроенная динамика неявно обернута средой выполнения в форму привязки? Потому что это работает, например:

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 следует предпочесть.

person Kevin    schedule 10.08.2016
comment
Теперь вижу, спасибо! Также поэтому я не удалось найти определение *warn-on-reflection* в clojure.core. - person OlegTheCat; 10.08.2016
comment
Возможно, вы захотите добавить, что вы, вероятно, можете заставить свою собственную динамическую переменную действовать так же, как с clojuredocs. org/clojure.core/push-thread-bindings. - person coredump; 11.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 - если вы не хотите копать глубже .

person birdspider    schedule 10.08.2016

Согласно ссылке на переменные, вы можете использовать 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
person superkonduktr    schedule 10.08.2016