当你写:
(def foo "a")
(def bar "b")
(def zip "c")
您已经定义了三个符号:foo
和bar
与zip
值相关联"a"
:"b"
和"c"
。
关联存储在 namsepace 表中,因此如果使用 REPL,命名空间将user
默认为,因此用户命名空间表现在将包含:
{foo
#'user/foo
bar
#'user/bar,
zip
#'user/zip}
您可以通过执行以下操作查看命名空间表:(ns-interns *ns*)
现在,如果你写:(foo bar zip)
在 Clojure 中,读者可以通过四种不同的方式来阅读它。您需要向读者指定应该以哪种方式阅读。这就是`
,'
并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
作为参数的关联值。该函数由 Clojure 在 clojure.core 命名空间内预定义;它返回它的参数列表。bar
zip
list
因此,当读者查找 时list
,它会找到 clojure.core 函数,然后查找foo
符号,并发现它映射到"a"
,依此类推。一旦找到符号的所有映射,它就会调用list
并将 的相关值传递给它foo bar zip
,这将是"a" "b" "c"
. 所以(list foo bar zip)
是一样的(list "a" "b" "c")
。
案例'
:
'(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)
实际上,反引号与引号`
非常相似,'
因为它不计算,而只是返回代码,除了它通过其中的完全限定符号来操纵要返回的代码。这对宏有影响,有时您可能需要完全限定的引用,从另一个命名空间中获取,有时您希望灵活地说,在当前命名空间中查找此符号。