Clojure中的符号绑定到底层对象并具有可选的单独值的基本原理是什么?也许是我缺少的基本内容,但如果有人能指出为什么会很好。
答案 0 :(得分:56)
任何Lisp中的符号都用作标识符。如果您要引用变量的值,比如说,您需要有一种方法来命名它;这就是符号的用途。请记住,所有Lisp代码都会在读取时转换为Lisp数据结构;标识符也必须由某些数据结构表示,它恰好是符号。遇到符号后,eval
将调度到某种“名称查找”操作。
从Lisp通用性转向Clojure细节,Clojure eval /编译器的行为是在遇到符号时,它将它作为let
引入的局部变量或函数参数或命名空间中条目的名称。实际上,在第一个容量中只能使用非命名空间限定的符号(意思是foo
而不是some-namespace/foo
形式的符号。
对于非命名空间限定符号foo
,如果找到名称为let
的{{1}}绑定/函数参数,则符号将计算其值。如果不是,则将符号转换为foo
形式(*ns*/foo
表示当前名称空间),并尝试在*ns*
中查找相应的条目;如果有这样的条目,则返回其值,否则抛出异常。
请注意,*ns*
这样的符号在名称空间identity
中使用时,将通过中间步骤解析为quux
,其中clojure.core/identity
下的条目被发现;这通常将引用到quux/identity
。这是一个在直观编码时没有想到的实现细节,但在尝试解释时我发现这是不可能的。
已经命名空间限定的符号(类似于名称空间clojure.core/identity
的{{1}} zip/root
到refer
而不clojure.zip
'的符号将会被查找在适当的命名空间中。
宏增加了一些复杂性(只能出现在操作员位置),但它与符号本身的行为并不相关。
请注意,在Clojure中,符号本身不是存储位置 - Vars。所以当我在上面说一个符号在命名空间中查找时,我的意思是use
查找由解析为其名称空间限定形式的符号命名的Var,然后获取该值。特殊形式eval
(通常缩写为var
)修改此行为,以便返回Var对象本身。但是,符号到瓦尔分辨率的机制没有改变。
请注意,所有这些意味着符号仅在#'
的意义上“绑定”到对象,在评估符号时,继续查找其他对象。符号本身没有“槽”或“字段”用于绑定到它的对象;符号被绑定到某个对象的任何印象都归因于eval
的工作原理。这是一个Clojure特性,因为在某些Lisps符号中它们本身就是存储位置。
最后,可以使用通常的引用机制来阻止对符号的评估:在eval
中,将不会评估符号'foo
(因此不会执行任何类型的名称查找) ;相反,它将保持不变。
回应OP的评论:试试这个很有趣:
foo
最后一个解释:(defmacro symbol?? [x]
(if (symbol? x)
true
false))
(def s 1)
(symbol? s)
; => false
(symbol?? s)
; => true
(symbol? 's)
; => true
(symbol?? 's)
; => false
是's
的简写;这是一个列表结构,而不是符号。一个宏对其直接传入的参数进行操作,而不进行评估;所以在(quote s)
中它实际上看到了(symbol?? 's)
列表结构,当然这本身并不是一个符号 - 尽管传递给(quote s)
时,它会评估为一个。
答案 1 :(得分:23)
在Common Lisp和Clojure中,术语“符号”的不同用法可能存在一些混淆。
在Common Lisp中,“符号”是内存中的位置,可以存储数据的位置。符号的“值”是存储在内存中该位置的数据。
在Clojure中,“符号”只是一个名字。它没有价值。
当Clojure编译器遇到符号时,它会尝试将其解析为
Var,正如之前的海报所指出的那样,代表了一个存储位置。
Clojure将Vars与Symbols分开是有充分理由的。首先,它避免了Common Lisp自动中断符号的烦恼,这些符号可以“污染”具有不需要符号的包。
其次,Clojure Vars在并发方面具有特殊的语义。 Var对所有线程都有一个“根绑定”可见。 (当您键入“def”时,您正在设置Var的根绑定。)对线程内的Var所做的更改(使用“set!”或“binding”)仅对该线程及其子项可见。