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

Jon*_*han 20 clojure

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)

  • @Jonathan根据我的经验,大多数时候使用向量而不是引用列表更方便.除了需要更少的击键之外,矢量更容易区分为未评估的列表而不是引用的列表. (2认同)

sku*_*uro 10

所不同的是,你可以看到,文字的语法'()引用.这意味着不评估内部符号.要在评估元素时使用列表文字,您可以利用syntax-quotereader宏:

user=> (apply str `(~foo ~bar ~zip))
"abc"
Run Code Online (Sandbox Code Playgroud)


Did*_* A. 5

当你写:

(def foo "a")
(def bar "b")
(def zip "c")
Run Code Online (Sandbox Code Playgroud)

您已经定义了三个符号:foobar以及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作为码元映射到的功能,以barzip作为符号映射到的值被传递到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映射到将采取相关的值的函数映射到foobarzip作为参数。该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。在我们的例子中,foobarzip是符号,(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)

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