【发布时间】:2026-01-12 23:50:02
【问题描述】:
我刚开始学习 Clojure,我对惰性序列的工作原理感到困惑。特别是,我不明白为什么这两个表达式会在 repl 中产生不同的结果:
;; infinite range works OK
(user=> (take 3 (map #(/(- % 5)) (range)))
(-1/5 -1/4 -1/3)
;; finite range causes error
user=> (take 3 (map #(/(- % 5)) (range 1000)))
Error printing return value (ArithmeticException) at clojure.lang.Numbers/divide (Numbers.java:188).
Divide by zero
我采用整数序列(0 1 2 3 ...) 并应用一个减去 5 然后取倒数的函数。显然,如果将其应用于 5,则会导致除零错误。但由于我只从惰性序列中获取前 3 个值,所以我没想到会看到异常。
结果是我使用所有整数时的预期结果,但如果我使用前 1000 个整数,则会出现错误。
为什么结果不同?
【问题讨论】:
-
Clojure 有时会将惰性序列上的操作分块,作为一种优化。为什么 Clojure 会分块一个表达式而不是另一个?我不知道。重要的是,在使用惰性序列时,代码需要使得实现比要求更多的元素仍能产生正确的行为。
-
感谢 Shannon,这确实解释了我所看到的行为,但我很失望地发现 Clojure 做到了这一点。这似乎违反了参照透明度——我帖子中的两个表达式应该产生相同的结果 IMO。这也意味着文档不是完全诚实的——“范围”文档说它返回一个惰性序列,但实际上这个序列是惰性的而不是惰性的。
-
我同意这令人困惑,但我认为您不能说它违反了参照透明度。你可以用什么表达式交换它的值来产生不同的结果? “问题”是
(range)和(range 999)产生的值的差异比预期的要多,但是您仍然可以将它们中的任何一个替换为它们的值并获得相同的结果。 -
@amalloy 我的问题是:我应该如何看待有异常的序列?如果我认为序列是完全惰性的,那么我的两个表达式都应该起作用。另一方面,如果我认为序列是完全评估的(不是字面意思),那么两个表达式都应该抛出异常。两种解释都不起作用。我想我的问题比参考透明度更基本。我的表达式甚至没有确定性值(因为它们可能会也可能不会根据分块引发异常)。我想我的收获是听从 ShannonSeverance 的建议并小心。