我对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)
答案 0 :(得分:3)
println
不是纯函数,您在第一次评估foo
时看到的是{em>副作用 { {1}}。当您第二次评估println
时,foo
未再次调用,因为println
的结果是已缓存。
您可以看到(map println [1 2 3])
是懒惰的,因为当您定义map
时,控制台中不会打印任何内容。只有在评估foo
时才会打印出来。
如果您使用纯功能,例如foo
:
inc
结果始终相同,没有任何副作用。 Clojure中的map
,filter
,etc旨在与纯函数一起使用,但该语言并不禁止您将它们与副作用。例如,在Haskell中,您甚至无法编写等效表达式,代码将无法编译。
答案 1 :(得分:2)
收藏集值。 println
返回的值为nil
。 println
的副作用是让您的屏幕上显示某些内容。
映射println
创建的值存储在var中。这是一个nil
值的惰性序列,由println
返回。
答案 2 :(得分:1)
只是详细说明你的问题。
println
仅对*out*
流产生副作用,默认情况下绑定到标准输出。
您可以同时从map
函数返回打印和某些值,例如
user> (defn print-and-inc [n]
(do
(println "called with n= " n)
(inc n)))
#'user/print-and-inc
do
将按顺序执行每个表达式,并在这种情况下返回最后一个(inc n)
的结果。
如果您现在将foo
定义为print-and-inc
与vector
int
的{{1}}的映射
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
保存的结果是初始集合的递增值。
注意:这可用于将信息记录/跟踪到您的代码中,但有一个标准库tools.logging
答案 3 :(得分:1)
除了其他人指出的内容之外,请注意在repl中尝试懒惰是有点问题的。懒惰的序列实际上没有值,直到通过某些使用该值的操作实现它们。在打印结果时,repl有一个隐式的doall来执行此操作。这意味着序列通常在您在repl中使用时实现,但在您的实际代码中使用它时可能不会。当您运行代码时,会得到意外的结果,因为序列尚未在您预期的位置实现,因为尚未调用repl隐式doall。作为一个如何引起混淆的例子,请看http://nicksellen.co.uk/2013/10/26/clojure-lazy-repl.html