【问题标题】:What's the difference between '() and (list) in Clojure?Clojure 中的 '() 和 (list) 有什么区别?
【发布时间】:2012-04-24 11:20:36
【问题描述】:

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 中有一个列表的“速记”(例如 {} 用于映射,[] 用于矢量),它是什么?

【问题讨论】:

  • 列表没有简写,因为列表对 Clojure 来说是次要的。

标签: clojure


【解决方案1】:

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

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

作为一个演示:

=> (def foo "a")
=> (type (first '(foo)))
clojure.lang.Symbol
=> (type (first (list foo))) 
java.lang.String

【讨论】:

  • @Jonathan 以我的经验,大多数时候使用向量而不是引用列表更方便。除了需要更少的击键外,向量更容易区分为未计算的列表而不是引用的列表。
  • 您能否也评论一下空箱'()(list)?它们之间有什么区别吗?
  • @Lii 不,我相信它们是一样的。
  • 只需()(无引号)也可以创建一个空列表。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    当你写作时:

    (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*)

    现在,如果您在 Clojure 中编写:(foo bar zip),那么读者可以通过四种不同的方式读取它。您需要向读者指定应该以哪种方式阅读。这就是`'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.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)),您会将符号列表传递给evaleval 会将符号评估为值并返回符号映射到的值列表。因此,您可以将引用 ' 视为将代码作为代码传递。

    `的案例:

    `(foo bar zip)
    

    这个有点复杂。读者将看到反引号` 并将递归解析符号列表中的符号以获得完全限定符号的列表。

    基本上,当阅读器在符号表中查找符号时,它会从当前命名空间的符号表中进行查找。完全限定符号是包含命名空间信息的符号。因此,当运行`(foo bar zip) 时,阅读器会将这些符号替换为完全限定的符号,将其转换为(user.foo user.bar user.zip)

    可以告诉读者评估列表中的一些元素,同时将其他元素更改为完全限定的符号。为此,您需要在要评估的符号前面加上 ~,如下所示:

    `(foo ~bar zip)
    

    会给你

    (clojure.foo "b" clojure.zip)
    

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

    【讨论】:

      猜你喜欢
      • 2011-04-23
      • 1970-01-01
      • 2011-05-04
      • 1970-01-01
      • 2017-08-25
      • 2016-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多