如何在此代码中运行延迟序列

时间:2014-07-02 05:29:16

标签: clojure lazy-sequences

代码在这里:

(def fib-seq (lazy-cat [0 1]  (map + (rest fib-seq) fib-seq )))

我可以理解fib-seq是一个惰性序列生成器,它生成一系列斐波那契数。
看看(take 5 fib-seq)我将得到斐波那契数字如下:
(0 1 1 2 3)

但我无法弄清楚在需要时如何生成延迟序列,所以我添加了一些副作用。

(def fib-seq (lazy-cat [0 1] (map + 
    (do (println "R") (rest fib-seq)) 
    (do (println "B") fib-seq))))

添加println我希望每当懒惰序列尝试在需要时生成新条目时打印RB,但不幸的是,结果是这样的。

user=> (take 5 fib-seq) ; this is the first time I take 5 elements
(0 R
B
1 1 2 3)

上面的输出看起来很奇怪,因为它不会逐个元素地打印R和B,但让我们来看看下一步。

第一次采取元素后:

user=> (take 20 fib-seq)
(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)

我再也不会收到RB,这让我感到困惑,因为它与我对懒惰序列生成的理解相矛盾。

能不能一步一步地向我解释?
顺便说一句,有没有可能有一个debug实用程序来调试它step by step就像JavaC一样?

2 个答案:

答案 0 :(得分:3)

好的,一步一步:

  1. 以下是lazy-catlink)的源代码:

    (defmacro lazy-cat
      "Expands to code which yields a lazy sequence of the concatenation
      of the supplied colls.  Each coll expr is not evaluated until it is
      needed. 
    
      (lazy-cat xs ys zs) === (concat (lazy-seq xs) (lazy-seq ys) (lazy-seq zs))"
      {:added "1.0"}
      [& colls]
      `(concat ~@(map #(list `lazy-seq %) colls)))
    

    所以你的代码:

    (def fib-seq (lazy-cat [0 1] (map + 
        (do (println "R") (rest fib-seq)) 
        (do (println "B") fib-seq))))
    

    扩展到:

    (def fib-seq (concat (lazy-seq [0 1])
                         (lazy-seq (map +
                                        (do (println "R") (rest fib-seq)) 
                                        (do (println "B") fib-seq)))))
    

    concat本身会返回一个惰性序列,这意味着在遍历concat之前不会评估fib-seq表单的正文。

  2. 当您第一次遍历fib-seq时(当您获取第一个5元素时),首先会评估concat表单的正文:

    (concat (lazy-seq [0 1])
            (lazy-seq (map +
                           (do (println "R") (rest fib-seq))
                           (do (println "B") fib-seq))))
    

    concat返回的延迟序列的前两个元素取自(lazy-seq [0 1]),后者从[0 1]获取;在此之后,[0 1]已用尽,(lazy-seq [0 1])也是如此,concat序列的下一个元素来自(lazy-seq (map ...))子序列。

    此处do特殊表单会被评估,并且您会看到RB已打印出来。 do的语义是评估其中的所有表单,然后返回最后一个表单的结果。

    因此(do (println "R") (rest fib-seq)打印R,然后返回(rest fib-seq)的结果,(do (println "B") fib-seq))打印B,然后返回fib-seq

    (map ...)返回一个懒惰的序列;当遍历到达fib-seq的第3个元素时,将评估map序列的第一个元素;它是fib-seq的第一个元素(即0)和(rest fib-seq)的第一个元素的总和,即fib-seq的第二个元素(即1)。两者都已经在此时进行了评估,因此我们不会以无限递归结束。

    对于下一个元素,map的懒惰会阻止无限递归的发生,并且会发生魔法。

  3. 在第二次遍历fib-seq(即(take 20 fib-seq))时,其前几个元素已经过评估,因此不会重新评估do特殊表单并且遍历继续没有副作用。


  4. 要在RB提取新元素时打印(rest fib-seq)fib-seq,您必须这样做:

    (def fib-seq
      (lazy-cat [0 1]
                (map + 
                     (map #(do (println "R") %) (rest fib-seq)) 
                     (map #(do (println "B") %) fib-seq)))))
    

答案 1 :(得分:0)

谢谢@omiel提供了许多有用的信息,但仍然没有碰到最敏感的一点,经过一段时间的思考,我弄清楚了懒的序列生成了什么。
如果我确实错了,请在clojure master进行解释。

我的意思是逐步实际关注懒惰序列项的生成,我已经知道了一些clojure语言的逻辑。

我们知道fib-seq被定义为lazy-seq,前两项是0和1,其余项目仍未评估,这是clojure最有趣的特征。登记/> 虽然很容易理解,访问前两个项目只是意味着触摸这两个东西,它们在内存中或缓存,因此它们可以直接返回并打印出来。

因为fib-seq目前没有第三项,所以当线程需要访问第3项时需要生成它,这是我的假设开始的地方:

由于(map + (rest fib-seq) fib-seq )本身就是lazy-seq,因此当前不包含任何项目并等待调用more命令。
这里调用fib-seq的第3项意味着调用惰性序列(map...)的第一项,因此需要生成并实际执行代码。
只需将变量名称替换为list,地图代码就像这样:

(map + (rest [0 1 ..]) [0 1 ..] ); the '..' means it is a lazy sequence

然后在rest执行后,此代码变为低于:

(map + [1 ..]  [0 1 ..] )
        ^       ^
        | ----- |
            |
            +
            1

map生成延迟序列时,会指示它生成它的第一项,因此通过map这两个列表,我们得到一个项目1=(+ 1 0),这是两者的结果这两个列表中的第一项加在一起。

然后map停止生成项目,因为它没有指示这样做。现在生成新项1并将其与[0 1]连接后,我们的fib-seq现在看起来像这样:

[0 1 1 ..]

非常好。现在让我们按fib-seq触摸(nth fib-seq 4)的第4项 fib-seq发现它不包含索引为4的项目,但它发现第三项已缓存,因此它将从4th生成3rd项。

现在线程移动到(map ...)功能并指示地图分发它的第二项 map发现它没有No.2项,所以它必须生成它。并用真正的懒惰seq替换fib-seq

(map + (rest [0 1 1..]) [0 1 1..] )

然后当然rest得到seq的剩余部分:

(map + [1 1..] [0 1 1..] )

这里发生了最棘手的事情 Map添加这些列表的第二项而不是第一项:

(map + [1 1..] [0 1 1..] )
          ^       ^
          | ----- |
              |
              +
              2

因此地图可以返回2作为其第二项,以便完成指令。

lazy-seq在指示时遵循以下项目中的相同策略,并将每个生成的项目缓存在内存中,以便更快地访问。

对于这个Fibonacci number generator,它只是移动两个列表并逐个添加它们并递归地生成所需的Fibonacci数字,如下所示:

0 1 1 2 3 5 ..   
1 1 2 3 5 ..

当然,这是一种非常灵巧的方式来生成Fibo

摘要

总而言之,从人类的角度来看,懒惰的seq将始终从其上一个状态/位置生成项目,而不是从其初始状态开始。

如果我错了,请纠正我,我是clojure的新手,我很乐意学习它。

顺便说一句

我想写netbeans language plugin clojureleiningen整合,因为我认为lighttable没用,有人有任何建议吗?