当你写代码时:
(def foo "a")
(def bar "b")
(def zip "c")
您定义了三个符号:
foo
,
bar
和
zip
,分别关联着值:
"a"
,
"b"
和
"c"
。
这些关联存储在命名空间表中。如果正在使用REPL,则默认情况下命名空间为
user
,因此,用户命名空间表现在将包含这些值。
{foo
bar
zip
您可以通过执行以下命令查看命名空间表:
(ns-interns *ns*)
。
现在,如果您在Clojure中编写
(foo bar zip)
,那么读取器会有4种不同的方式来读取它。您需要向读取器指定应该如何读取它。这就是
`
、
'
和
list
发挥作用的地方。
没有指示符的情况下:
(foo bar zip)
如果没有任何指示,读者会将其解释为一个S表达式,并将foo
解释为映射到函数的符号,而bar
和zip
则是映射到传递给foo
函数的值的符号。
在我们的情况下,它会抛出异常:
java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn
这是因为没有定义函数
foo
,
foo
被关联到了一个字符串
"a"
,而不是一个IFn(Clojure函数)。
如果定义了函数
foo
,它将会调用
foo
并将
"b"
和
"c"
作为参数传递进去。
list
的情况:
(list foo bar zip)
当使用列表符号时,阅读器实际上将其解释为没有指示器的情况。也就是说,它正在寻找一个映射到函数的符号
list
,该函数将以与
foo
、
bar
和
zip
映射的相应值作为参数。
list
函数由Clojure在clojure.core命名空间内预定义;它返回其参数的列表。
当阅读器查找
list
时,它会找到Clojure核心函数,然后查找
foo
符号,并发现它映射到
"a"
等等。一旦找到了所有符号的映射,它调用
list
并传递
foo bar zip
的相关值,即
"a" "b" "c"
。因此,
(list foo bar zip)
与
(list "a" "b" "c")
相同。
< p >
'的情况:
'(foo bar zip)
'
引用告诉读者,跟随的表单将按原样阅读。在我们的例子中,
foo
、
bar
和
zip
是符号,
(foo bar zip)
是符号列表。因此,读者将把这个列表解释为符号列表。
这就是为什么当你运行
(apply str '(foo bar zip))
时,它会调用
str 'foo 'bar 'zip
,从而给你
foobarzip
。也就是说,它将把符号列表中的每个符号转换为一个字符串表示,并将其连接成一个字符串。
通过直接使用表单作为参数,它将传递一个符号列表而不是评估符号,即不寻找它们关联的内容。如果你运行了
(eval '(foo bar zip))
,那么你将向
eval
传递一个符号列表,
eval
将计算符号的值并返回映射到符号的值的值列表。所以你可以认为引号
'
是将代码作为代码传递。
`
的情况:
`(foo bar zip)
这有点复杂。读者将看到反引号
`
并递归解析符号列表中的符号,以获取完全合格的符号列表。基本上,当读者在符号表中查找符号时,它是从当前命名空间的符号表中进行的。完全合格的符号是包括命名空间信息的符号。因此,在运行
`(foo bar zip)
时,阅读器将使用完全合格的符号替换这些符号,并将其转换为
(user.foo user.bar user.zip)
。可以告诉阅读器评估列表中的某些元素,同时将其他元素更改为完全限定符号。要做到这一点,您需要使用前缀
~
来评估所需符号,例如:
`(foo ~bar zip)
将会给你
(clojure.foo "b" clojure.zip)
有效地说,反引号
`
与引号
'
非常相似,因为它不会评估,只返回代码,但它通过完全限定其中的符号来操作要返回的代码,这对于宏有影响,有时您可能需要一个完全限定的引用,以从另一个命名空间获取,有时你想灵活地说,在当前命名空间中查找此符号。