当你写作时:
(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*)
现在,如果您在 Clojure 中编写:(foo bar zip),那么读者可以通过四种不同的方式读取它。您需要向读者指定应该以哪种方式阅读。这就是`、' 和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.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)
实际上,反引号` 与引用' 非常相似,因为它不计算,而只是返回代码,只是它通过其中的完全限定符号来操作要返回的代码。这对宏有影响,有时您可能需要完全限定的引用,从另一个命名空间中获取,有时您希望灵活地说,在当前命名空间中查找此符号。