Lists上的API Cheatsheet部分似乎表明它'()是一个列表构造函数,就像(list),但我发现在实践中它们并不完全相同.例如,给定:
(def foo "a")
(def bar "b")
(def zip "c")
Run Code Online (Sandbox Code Playgroud)
以下声明:
(apply str '(foo bar zip))
Run Code Online (Sandbox Code Playgroud)
产生输出"foobarzip",我不指望.
但据称相当于:
(apply str (list foo bar zip))
Run Code Online (Sandbox Code Playgroud)
正如我所料,产生"abc".
这里发生了什么?如果在Clojure中有一个列表的"简写"(比如{}地图和[]向量),它是什么?
huo*_*uon 34
在lisps中,'(like quote)引用它的参数,即保留它们几乎与它们的s-exp形式一样,包括不评估其中的任何内容.
换一种方式'(foo bar zip)创建一个包含符号列表foo,bar,zip,而(list foo bar zip)创建包含一个列表值的foo,bar,zip.在第一种情况下,str将符号本身转换为字符串,然后将它们连接起来.
作为对此的证明:
=> (def foo "a")
=> (type (first '(foo)))
clojure.lang.Symbol
=> (type (first (list foo)))
java.lang.String
Run Code Online (Sandbox Code Playgroud)
sku*_*uro 10
所不同的是,你可以看到,文字的语法'()被引用.这意味着不评估内部符号.要在评估元素时使用列表文字,您可以利用syntax-quotereader宏:
user=> (apply str `(~foo ~bar ~zip))
"abc"
Run Code Online (Sandbox Code Playgroud)
当你写:
(def foo "a")
(def bar "b")
(def zip "c")
Run Code Online (Sandbox Code Playgroud)
您已经定义了三个符号:foo,bar以及zip相关的使用值:"a","b"和"c"。
关联存储在namsepace表中,因此,如果使用REPL,则user默认情况下名称空间为,因此用户名称空间表现在将包含:
{foo
#'user/foo
bar
#'user/bar,
zip
#'user/zip}
Run Code Online (Sandbox Code Playgroud)
您可以通过执行以下操作查看名称空间表: (ns-interns *ns*)
现在,如果您写:(foo bar zip)在Clojure中,将有四种不同的方式可以被读者读取。您需要向读者指定应该以哪种方式阅读。这就是`,'并list开始发挥作用。
没有指示器的情况:
(foo bar zip)
Run Code Online (Sandbox Code Playgroud)
当简单地写没有任何指示器,读者将其解释为一个S-表达和将解释foo作为码元映射到的功能,以bar及zip作为符号映射到的值被传递到foo功能。
在我们的情况下,它将引发异常:
java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn
Run Code Online (Sandbox Code Playgroud)
这是因为foo未定义foo与函数关联的函数"a",后者是String而不是IFn(Clojure函数)。
如果foo定义了函数,则将调用foo传递为实参"b"和"c"。
情况list:
(list foo bar zip)
Run Code Online (Sandbox Code Playgroud)
当使用列表符号时,读者实际上以与没有指示符的情况相同的方式解释这种情况。也就是说,它在寻找一个符号list映射到将采取相关的值的函数映射到foo,bar并zip作为参数。该list函数由clojure.core命名空间内的Clojure预先定义;它返回其参数列表。
因此,当读者寻找时list,它会找到clojure.core函数,然后寻找该foo符号,并发现它映射到"a",依此类推。一旦它找到的所有映射的符号,它调用list并将其传递的相关联的值foo bar zip,这将是"a" "b" "c"。因此(list foo bar zip)与相同(list "a" "b" "c")。
情况':
'(foo bar zip)
Run Code Online (Sandbox Code Playgroud)
该'报价告诉读者接下来的表格将被解读为IS。在我们的例子中,foo,bar和zip是符号,(foo bar zip)是一个符号列表。因此,读者会将其解释为符号列表。
这就是为什么在您运行时(apply str '(foo bar zip))它会打电话str 'foo 'bar 'zip给您的原因foobarzip。也就是说,它将把符号列表中的每个符号转换为String表示形式,然后将它们串联为一个String。
通过采用原样的形式,它传递了一个符号列表作为参数,而不评估符号,即,不查找它们的关联。如果您运行了(eval '(foo bar zip)),则将符号列表传递给eval,并将eval符号评估为值,然后返回符号映射到的值的列表。因此,您可以将引号'视为将代码作为代码传递。
情况`:
`(foo bar zip)
Run Code Online (Sandbox Code Playgroud)
这一点有点复杂。读者将看到反引号`,并将递归解析符号列表中的符号,以获取完全限定符号的列表。
基本上,当读者在符号表中查找符号时,它会从当前名称空间的符号表中进行查找。完全限定的符号是包含名称空间信息的符号。因此,运行`(foo bar zip)阅读器时,会将这些符号替换为合格的符号,并将其转换为(user.foo user.bar user.zip)。
可以告诉读者评估列表中的某些元素,同时将其他元素更改为完全合格的符号。为此,您可以为要计算的符号添加前缀,~如下所示:
`(foo ~bar zip)
Run Code Online (Sandbox Code Playgroud)
会给你
(clojure.foo "b" clojure.zip)
Run Code Online (Sandbox Code Playgroud)
实际上,反引号与引号`非常相似',因为它不求值,而只是返回代码,只是它通过完全限定其中的符号来操纵要返回的代码。这对宏有影响,在宏中,有时您可能希望从另一个名称空间中获取完全合格的引用,而有时您想要灵活地说,在当前名称空间中查找此符号。