为啥 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?

Posted

技术标签:

【中文标题】为啥 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?【英文标题】: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 或根本没有值。空列表主要作为递归过程的基本情况返回。 【参考方案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 的答案提供了所有信息,但可能太多了)

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

看这个例子:

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

你可以写

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

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


附言

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

【讨论】:

以上是关于为啥 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?的主要内容,如果未能解决你的问题,请参考以下文章

为啥会返回这个矛盾的 clojure.core.logic/featurec 结果?

为啥不使用 UIPickerView 调用属性TitleForRow?

Clojure:让作用域和函数返回值

从clojure中的普通lisp替换(null x)函数的惯用方法

clojure jdbc postgres:为啥我的查询结果将表名中的 unicode 字符返回为 �?

在 Clojure 中类型提示一个 nil 文字