Clojure中'()和(list)有什么区别?

23

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中有列表的“快捷方式”(例如映射使用{},向量使用[]),那么它是什么?


没有列表的速记方式,因为在Clojure中,列表是次要的。 - Marko Topolnik
3个回答

38

在Lisp中,' (比如 quote)会引用其参数,即几乎完全保留它们在S表达式中的写法,包括不评估其中任何内容。

换句话说,'(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

2
@Jonathan 在我的经验中,大多数情况下使用向量比使用引用列表更方便。除了需要更少的按键之外,向量作为未评估列表比引用列表更容易区分。 - user100464
你能否也评论一下空列表的情况,'()(list)?它们之间有什么区别吗? - Lii
@Lii 不,我相信它们是一样的。 - huon
只需使用 ()(不需要引号)即可创建一个空列表。 - Xavi

10

区别在于,正如您所看到的,文字语法'()被引用了。这意味着括号内的符号不会被评估。要在评估元素时使用列表字面值,您可以利用syntax-quote阅读器宏:

user=> (apply str `(~foo ~bar ~zip))
"abc"

7

当你写代码时:

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

您定义了三个符号: foobarzip,分别关联着值:"a""b""c"
这些关联存储在命名空间表中。如果正在使用REPL,则默认情况下命名空间为user,因此,用户命名空间表现在将包含这些值。
{foo
#'user/foo
bar
#'user/bar,
zip
#'user/zip}

您可以通过执行以下命令查看命名空间表:(ns-interns *ns*)
现在,如果您在Clojure中编写(foo bar zip),那么读取器会有4种不同的方式来读取它。您需要向读取器指定应该如何读取它。这就是`'list发挥作用的地方。
没有指示符的情况下:
(foo bar zip)

如果没有任何指示,读者会将其解释为一个S表达式,并将foo解释为映射到函数的符号,而barzip则是映射到传递给foo函数的值的符号。

在我们的情况下,它会抛出异常:

java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn

这是因为没有定义函数foofoo被关联到了一个字符串"a",而不是一个IFn(Clojure函数)。
如果定义了函数foo,它将会调用foo并将"b""c"作为参数传递进去。 list的情况:
(list foo bar zip)

当使用列表符号时,阅读器实际上将其解释为没有指示器的情况。也就是说,它正在寻找一个映射到函数的符号list,该函数将以与foobarzip映射的相应值作为参数。 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)
'引用告诉读者,跟随的表单将按原样阅读。在我们的例子中,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)

有效地说,反引号`与引号'非常相似,因为它不会评估,只返回代码,但它通过完全限定其中的符号来操作要返回的代码,这对于宏有影响,有时您可能需要一个完全限定的引用,以从另一个命名空间获取,有时你想灵活地说,在当前命名空间中查找此符号。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接