关于 Clojure 的惰性

About Clojure's Laziness

我对 clojure 的惰性序列感到好奇。在REPL中,我定义了一个变量foo:

user> (def foo (map println [1 2 3]))
#'user/foo

第一次 评估 foo 时,它似乎有效:

user> foo
1
2
3
(nil nil nil)

但是第一次之后,为什么就变懒了?

user> foo
(nil nil nil)

println 不是 函数,您在第一次计算 foo 时看到的是 println 的影响 。当您第二次计算 foo 时,不会再次调用 println,因为 (map println [1 2 3]) 的结果是 cached

而且您可以看到 map 是惰性的,因为当您定义 foo 时,控制台中不会打印任何内容。只有在评估 foo 时才会打印一些东西。

请参阅Laziness in Clojure

如果您使用 函数,例如 inc:

(def foo (map inc [1 2 3]))

> foo
(2 3 4)

> foo
(2 3 4)

结果总是一样的,没有任何副作用。 Clojure 中的 map, filter, etc 旨在与 函数一起使用,但该语言并不禁止您将它们与具有 副作用的函数一起使用 .在 Haskell 中,例如你甚至不能写一个等价的表达式,代码将无法编译。

集合具有价值。 println 返回的值为 nilprintln 的副作用是让一些东西出现在你的屏幕上。

映射 println 创建的值存储在您的变量中。这是 nil 值的惰性序列,由 println.

返回

只是为了详细说明你的问题。 println 仅对默认绑定到标准输出的 *out* 流有副作用。

您可以从您 map 的函数中获得打印和 return 某些值,例如

user> (defn print-and-inc [n]
        (do
          (println "called with n= " n)
          (inc n)))
#'user/print-and-inc   

do 将按顺序执行每个表达式,return 最后一个的结果,在本例中为 (inc n)。 如果您现在将 foo 定义为 print-and-incint

vector 上的映射
user> (def foo (map print-and-inc [1 2 3 4 5]))
#'user/foo
user> 
user> foo
called with n=  1
called with n=  2
called with n=  3
called with n=  4
called with n=  5
(2 3 4 5 6)
user> 
user> foo
(2 3 4 5 6)

并且您看到了 map 的惰性,因为打印仅在第一次调用 foo 时发生。但是现在 foo 保存的结果是初始集合的增量值。

注意:这可用于 log/trace 信息到您的代码中,但是有一个标准库 tools.logging

除了其他人所指出的之外,请注意在 repl 中尝试惰性是有点问题的。惰性序列在通过使用该值的某些操作实现之前实际上没有值。 repl 在打印结果时有一个隐式的 doall 来执行此操作。这意味着序列通常在您在 repl 中使用时实现,但在您的实际代码中使用时可能不会。当您 运行 您的代码时,您会得到意想不到的结果,因为序列没有在您预期的位置实现,因为 repl implicit doall 尚未被调用。作为这如何导致混乱时刻的示例,请查看 http://nicksellen.co.uk/2013/10/26/clojure-lazy-repl.html