【问题标题】:Why Clojure idiom prefer to return nil instead of empty list like Scheme?为什么 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?
【发布时间】:2011-08-28 01:46:08
【问题描述】:

来自a comment on another question,有人说Clojure 习惯用法更喜欢返回nil 而不是像Scheme 中那样的空列表。这是为什么呢?

喜欢,

(when (seq lat) ...)

而不是

  (if (empty? lat)  
    '() ...)

【问题讨论】:

  • 我猜this thread 应该有助于解决这个问题。
  • 返回一个虚假值肯定比返回一个真实值更有用。其他 lisp(如 Common Lisp)将空列表视为虚假列表。 Clojure 没有效仿,如 skuro 链接中的答案所述。如果(if (seq some-list) ... always 执行第一个表单,我会感到惊讶。
  • Scheme 经常不返回空列表,通常 Scheme 过程会返回 #f 或根本没有值。空列表主要作为递归过程的基本情况返回。

标签: clojure idioms


【解决方案1】:

来自The Joy of Clojure

因为空集合在布尔上下文中的行为类似于 true,所以您需要一个习惯用法来测试集合中是否有任何要处理的内容。值得庆幸的是,Clojure 提供了这样一种技术:

(seq [1 2 3])
;=> (1 2 3)

(seq [])
;=> nil

在其他 Lisp 中,例如 Common Lisp,空列表用于表示 nil。这被称为 nil punning,只有在空列表为假时才可行。在这里返回 nil 是 clojure 重新引入 nil 双关语的方式。

【讨论】:

    【解决方案2】:

    还请注意,集合类型和 nil 的并集形成了一个幺半群,将幺半群加号连接起来,将幺半群零连接起来。因此,nil 将空列表语义保持在串联之下,同时还表示错误或“缺失”值。

    Python 是另一种语言,其中常见的 monoid 标识表示错误值:0、空列表、空元组。

    【讨论】:

    • 你知道如果你有加法,任何n + 0 = n?如果你有逻辑或,任何 n 或 true = n?如果你在 Clojure 中有列表连接,any (concat n nil) = n?因此,运营中存在这种“遗漏”的模式。 Clojure 使用 nil 来表示许多情况下“缺失”的概念,因此您可以使常用算法(例如 tree walkers)更通用和更清晰。
    【解决方案3】:

    我能想到几个原因:

    • 逻辑区别。在 Clojure 中,nil 意味着什么都没有/没有价值。而 '() "空列表 是 一个值 - 它恰好是一个空列表的值。区分两者在概念上和逻辑上通常很有用。

    • 适合 JVM - JVM 对象模型支持空引用。并且相当多的 Java API 返回 null 表示“无”或“未找到值”。因此,为了确保轻松的 JVM 互操作性,Clojure 以类似的方式使用 nil 是有意义的。

    • 懒惰 - 这里的逻辑相当复杂,但我的理解是,将 nil 用于“无列表”更适合 Clojure 的 lazy sequences。由于 Clojure 默认是一种惰性函数式编程语言,因此将这种用法作为标准是有意义的。更多解释请参见http://clojure.org/lazy。

    • “Falsiness” - 在编写检查集合的条件代码时,使用 nil 表示“无”和表示“假”很方便 - 所以你可以编写像 @987654324 这样的代码@ 测试哈希映射是否包含给定键的值。

    • 性能 - 测试 nil 比检查列表以查看它是否为空更有效...因此采用此成语作为标准可以带来更高性能的惯用代码

    请注意,Clojure 中仍有一些函数会返回一个空列表。一个例子是休息:

    (rest [1])
    => ()
    

    question on rest vs. next 详细说明了为什么会这样.....

    【讨论】:

    • FWIW,如果您将() 作为文字返回,调用者可以快速测试它,因为它的类将是clojure.lang.PersistentList$EmptyList。检查肯定比测试empty? 或seq 更快,但可能仍然比检查nil 慢一些。
    【解决方案4】:

    既然我写了评论,我会写一个答案。 (skuro 的答案提供了所有信息,但可能太多了)

    1. 首先,我认为更重要的事情应该放在第一位。
    2. seq 是每个人大部分时间都在使用的东西,但 empty? 对它只是 (not (seq lat)) 很好
    3. 在 Clojure 中,'() 为真,所以通常你希望在序列完成时返回假的。
    4. 如果你只有一个 importend 分支,如果另一个返回 false/'() 或类似的东西,你为什么要写下那个分支。 when 只有一个分支,如果你想产生副作用,这特别好。您不必使用do。

    看这个例子:

    (如果为假 '() (做 (println 1) (println 2) (println 3)))

    你可以写

    (如果为真 (println 1) (println 2) (println 3))

    没有那么不同,但我认为它更好地阅读。


    附言

    并不是说有称为 if-not 和 when-not 的函数,它们通常比 (if (not true) ...) 更好

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-13
      • 1970-01-01
      • 1970-01-01
      • 2014-03-02
      • 2011-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多