为啥 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-not
和 when-not
的函数,它们通常比 (if (not true) ...)
更好
【讨论】:
以上是关于为啥 Clojure 成语更喜欢返回 nil 而不是像 Scheme 这样的空列表?的主要内容,如果未能解决你的问题,请参考以下文章
为啥会返回这个矛盾的 clojure.core.logic/featurec 结果?
为啥不使用 UIPickerView 调用属性TitleForRow?
从clojure中的普通lisp替换(null x)函数的惯用方法