Clojure的惰性求值如何与调用Java/不纯代码交互?

8

今天我们在代码中遇到了一个问题,无法回答这个Clojure问题:

Clojure是否严格或惰性地评估不纯的代码(或对Java代码的调用)?

似乎副作用和惰性序列会导致奇怪的行为。


以下是我们了解到的信息:

Clojure有惰性序列:

user=> (take 5 (range)) ; (range) returns an infinite list
(0 1 2 3 4)

而 Clojure 存在副作用和不纯函数:

user=> (def value (println 5))
5                               ; 5 is printed out to screen
user=> value
nil                             ; 'value' is assigned nil

此外,Clojure可以调用Java对象,这些对象可能包含副作用。然而,副作用可能与惰性求值相互影响不良:
user=> (def my-seq (map #(do (println %) %) (range)))
#'user/my-seq
user=> (take 5 my-seq)                               
(0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
0 1 2 3 4)

它返回了前5个元素,但打印了前31个元素!

我认为如果在Java对象上调用具有副作用的方法可能会出现类似的问题。这将使得推理代码并弄清楚会发生什么变得非常困难。


附加问题:

  • 程序员是否需要注意并预防这种情况?(是的?)
  • 除了序列之外,Clojure是否执行严格评估?(是的?)
2个回答

8
Clojure的惰性序列会将大约30个项目分块,因此开销很小。这不是纯粹主义者的选择,而是一种实用的选择。请参阅《Clojure之乐》以了解一个按顺序实现一个元素的普通解决方案。
惰性序列并不完全适合于不纯函数,原因就像你遇到的那样。
Clojure也可以进行严格评估,但是对于宏来说情况有些不同。内置函数(如if)自然会保留评估。

2
这不是一个如何混合副作用和惰性的问题 - 这是一个“哇,那很奇怪,为什么会发生这种情况,我们如何避免这种情况?”的问题。我的理解是:Clojure严格评估不纯/Java调用,这是程序员的责任,不要混合不纯和惰性? - Matt Fenwick
3
@MattFenwick,基本上Clojure始终是严格求值的。惰性序列基本上使用与Python中的生成器等相同的技巧。严格求值却穿着惰性计算的外衣。 :) - mike3996

2

惰性结构会在实现方便的时候进行评估,无论其中引用了什么。所以,是的,程序员需要小心并在需要时强制实现惰性序列。

我不知道你所说的严格评估是什么意思。


1
好的,正如progo所说,Clojure在函数调用和绑定方面是严格评估的。宏可以随意处理它们的参数。 (惰性)序列是抽象的,严格/急切的评估并不直接适用于它们; 惰性序列评估为自身 - 只有当您尝试检查其中的元素时,您才确定这些值已被实现(尽管在那之前可能已经发生了)。 - Joost Diepenmaat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接