【问题标题】:About Clojure's Laziness关于 Clojure 的懒惰
【发布时间】:2015-02-06 13:59:19
【问题描述】:

我对 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)

【问题讨论】:

    标签: clojure


    【解决方案1】:

    println 不是一个纯函数,您在第一次评估foo 时看到的是println 的副作用。当您第二次评估foo 时,不会再次调用println,因为(map println [1 2 3]) 的结果是缓存。

    您可以看到 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 中,您甚至无法编写等效表达式,代码将无法编译。

    【讨论】:

    • 所以,(map println [1 2 3]) 的结果在第一次运行后被缓存;缓存的结果不是序列本身,而是一个 seqal 对象。我的理解对吗?
    • 结果是一个seqable,其中包含将println 应用于序列[1 2 3] 的每个元素的结果。但是println 返回nil,所以map 返回的seqable 是(nil nil nil)。
    【解决方案2】:

    集合保存值。 println 返回的值为nil。 println 的副作用是让某些东西出现在你的屏幕上。

    通过映射 println 创建的值存储在您的 var 中。这是nil 值的惰性序列,由println 返回。

    【讨论】:

      【解决方案3】:

      只是为了详细说明您的问题。 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 在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 保存的是初始集合的递增值。

      注意:这可用于将信息记录/跟踪到您的代码中,但有一个标准库 tools.logging

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 这篇文章对于新的 clojure 开发者来说非常重要。非常感谢!
        猜你喜欢
        • 2010-12-17
        • 2020-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多