关于 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
时才会打印一些东西。
如果您使用 纯 函数,例如 inc
:
(def foo (map inc [1 2 3]))
> foo
(2 3 4)
> foo
(2 3 4)
结果总是一样的,没有任何副作用。 Clojure 中的 map
, filter
, etc 旨在与 纯 函数一起使用,但该语言并不禁止您将它们与具有 副作用的函数一起使用 .在 Haskell 中,例如你甚至不能写一个等价的表达式,代码将无法编译。
集合具有价值。 println
返回的值为 nil
。 println
的副作用是让一些东西出现在你的屏幕上。
映射 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-inc
在 int
的 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
我对 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
时才会打印一些东西。
如果您使用 纯 函数,例如 inc
:
(def foo (map inc [1 2 3]))
> foo
(2 3 4)
> foo
(2 3 4)
结果总是一样的,没有任何副作用。 Clojure 中的 map
, filter
, etc 旨在与 纯 函数一起使用,但该语言并不禁止您将它们与具有 副作用的函数一起使用 .在 Haskell 中,例如你甚至不能写一个等价的表达式,代码将无法编译。
集合具有价值。 println
返回的值为 nil
。 println
的副作用是让一些东西出现在你的屏幕上。
映射 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-inc
在 int
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