对惰性序列的评估感到困惑

mor*_*ode 5 functional-programming clojure lazy-evaluation

我正在尝试 Clojure 的惰性序列。为了查看某个项目的评估何时发生,我创建了一个名为 square 的函数,该函数在返回结果之前打印结果。然后我使用地图将此函数应用于向量。

(defn square [x]
  (let [result (*  x x)]
  (println "printing " result)
  result))

(def s (map square [1 2 3 4 5])) ; outputs nothing
Run Code Online (Sandbox Code Playgroud)

在我的 s 声明中,REPL 不输出任何内容。这表明计算尚未开始。这似乎是正确的。然后我这样做:

(first s)
Run Code Online (Sandbox Code Playgroud)

函数“first”仅采用第一项。所以我预计只会评估 1 个。我的期望是 REPL 将输出以下内容:

printing 1
1
Run Code Online (Sandbox Code Playgroud)

然而,REPL 输出了以下内容。

printing 1
printing 4
printing 9
printing 16
printing 25
1
Run Code Online (Sandbox Code Playgroud)

因此,它似乎不是只评估第一个项目,而是评估所有项目,即使我只访问第一个项目。

如果惰性序列的状态只能是计算所有值和不计算值,那么它如何获得惰性求值的优势呢?我来自计划背景,我期待更像流的行为。看来我错了。谁能解释一下发生了什么事吗?

Lee*_*Lee 9

惰性并不是全有或全无,但 seq 的某些实现对输入序列的“块”进行操作(请参阅此处以获取解释)。这是向量的情况,您可以使用以下命令进行测试chunked-seq?

(chunked-seq? (seq [1 2 3 4 5]))
Run Code Online (Sandbox Code Playgroud)

当给定一个集合时,map 检查底层序列是否被分块,如果是,则基于每个块而不是一次评估一个元素。

块大小通常为 32,因此您可以通过比较结果来看到此行为

(first (map square (vec (range 35))))
Run Code Online (Sandbox Code Playgroud)

这应该只显示前 32 个项目的消息,而不是整个序列。