21

Lists 上的API Cheatsheet部分似乎表明这'()是一个列表构造函数,就像(list),但我发现在实践中它们并不完全相同。例如,给定:

(def foo "a")
(def bar "b")
(def zip "c")

以下声明:

(apply str '(foo bar zip))

产生输出“foobarzip”,这是我没想到的。

但应该是等价的:

(apply str (list foo bar zip))

正如我所料,产生“abc”。

这里发生了什么?如果 Clojure 中有一个列表的“速记”(例如{}地图和[]向量),它是什么?

4

3 回答 3

36

在 lisps 中,'(like quote) 引用它的参数,即保留它们几乎完全按照它们的 s-exp 形式编写,包括不评估其中的任何内容。

换句话说,'(foo bar zip)创建一个包含符号foo, bar, zip;的列表 while(list foo bar zip)创建一个包含, ,的的列表。在第一种情况下,将符号本身转换为字符串,然后将它们连接起来。foobarzipstr

作为对此的示范:

=> (def foo "a")
=> (type (first '(foo)))
clojure.lang.Symbol
=> (type (first (list foo))) 
java.lang.String
于 2012-04-24T11:31:38.990 回答
10

不同之处在于,如您所见,文字语法'()是带引号的。这意味着内部的符号不会被评估。要在评估元素时使用列表文字,您可以利用syntax-quote阅读器宏:

user=> (apply str `(~foo ~bar ~zip))
"abc"
于 2012-04-24T11:33:35.060 回答
6

当你写:

(def foo "a")
(def bar "b")
(def zip "c")

您已经定义了三个符号:foobarzip值相关联"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 命名空间内预定义;它返回它的参数列表。barziplist

因此,当读者查找 时list,它会找到 clojure.core 函数,然后查找foo符号,并发现它映射到"a",依此类推。一旦找到符号的所有映射,它就会调用list并将 的相关值传递给它foo bar zip,这将是"a" "b" "c". 所以(list foo bar zip)是一样的(list "a" "b" "c")

案例'

'(foo bar zip)

引文告诉读者,下面'的形式应按原样阅读。在我们的例子中foobarzip是符号,并且(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)

实际上,反引号与引号`非常相似,'因为它不计算,而只是返回代码,除了它通过其中的完全限定符号来操纵要返回的代码。这对宏有影响,有时您可能需要完全限定的引用,从另一个命名空间中获取,有时您希望灵活地说,在当前命名空间中查找此符号。

于 2015-10-14T00:31:26.017 回答