为什么我可以设置!内置动态(?)Clojure 变量?
Why I can set! builtin dynamic (?) Clojure vars?
为什么我可以这样做:
> (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
,但请务必遵循文档的建议,并在 finally 块中将 try/catch/finally 和 pop-thread-bindings
包裹起来。那时你会重新实现 binding
(例如 运行 (source binding)
在 repl),这可能就是为什么文档明确警告 push-thread-bindings
是一个低level函数和binding
应该是首选。
仔细阅读文档,您会发现
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.
特别是:
Implies that set!'ing the provided vars will succeed
所以,这意味着您可以检查 set!
是否可行,如下所示:
user=> (thread-bound? #'*x*)
false
user=> (thread-bound? #'*unchecked-math*)
true
这意味着您只能 set!
线程绑定变量,而您的 *x*
还没有。
PS:在 Kevins 答案中,您会看到 Var.pushThreadBindings
,这大概是 clojure.core/push-thread-bindings
可用的 - 如果您不想深入挖掘的话。
根据 reference on Vars,您只能对线程绑定变量使用 set!
赋值:
Currently, it is an error to attempt to set the root binding of a var
using set!, i.e. var assignments are thread-local. In all cases the
value of expr is returned.
但是,像 *warn-on-reflection*
这样的内置动态变量具有线程本地绑定,因此您可以自由地在它们上使用 set!
:
(thread-bound? #'*unchecked-math*)
=> true
而 (def ^:dynamic *x*)
仅创建根绑定:
(thread-bound? #'*x*)
=> false
binding
宏为动态 Var 创建了一个新的作用域;在低级别上,它会在退出宏主体时暂时 'pushes' bound values of given Vars to the current thread, and then 'pops' 它们。
(binding [*x* 1]
(thread-bound? #'*x*))
=> true
(binding [*x* 1]
(set! *x* 2))
=> 2
为什么我可以这样做:
> (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
,但请务必遵循文档的建议,并在 finally 块中将 try/catch/finally 和 pop-thread-bindings
包裹起来。那时你会重新实现 binding
(例如 运行 (source binding)
在 repl),这可能就是为什么文档明确警告 push-thread-bindings
是一个低level函数和binding
应该是首选。
仔细阅读文档,您会发现
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.
特别是:
Implies that set!'ing the provided vars will succeed
所以,这意味着您可以检查 set!
是否可行,如下所示:
user=> (thread-bound? #'*x*)
false
user=> (thread-bound? #'*unchecked-math*)
true
这意味着您只能 set!
线程绑定变量,而您的 *x*
还没有。
PS:在 Kevins 答案中,您会看到 Var.pushThreadBindings
,这大概是 clojure.core/push-thread-bindings
可用的 - 如果您不想深入挖掘的话。
根据 reference on Vars,您只能对线程绑定变量使用 set!
赋值:
Currently, it is an error to attempt to set the root binding of a var using set!, i.e. var assignments are thread-local. In all cases the value of expr is returned.
但是,像 *warn-on-reflection*
这样的内置动态变量具有线程本地绑定,因此您可以自由地在它们上使用 set!
:
(thread-bound? #'*unchecked-math*)
=> true
而 (def ^:dynamic *x*)
仅创建根绑定:
(thread-bound? #'*x*)
=> false
binding
宏为动态 Var 创建了一个新的作用域;在低级别上,它会在退出宏主体时暂时 'pushes' bound values of given Vars to the current thread, and then 'pops' 它们。
(binding [*x* 1]
(thread-bound? #'*x*))
=> true
(binding [*x* 1]
(set! *x* 2))
=> 2