0

代码在这里:

(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我希望它打印出来R,并且B每当惰性序列在需要时尝试生成新条目时,但不幸的是结果是这样的。

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)

我再也不会收到RandB了,这让我很困惑,因为它与我对惰性序列生成的理解相冲突。

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

4

2 回答 2

4

好的,一步一步来:

  1. lazy-cat这是(链接)的源代码:

    (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的第三个元素时,map评估序列的第一个元素;它是 (ie ) 的第一个元素和fib-seq(ie )0(rest fib-seq)第二个元素的总和。此时两者都已被评估,因此我们不会以无限递归结束。fib-seq1

    对于接下来的元素, 的惰性map阻止了无限递归的发生,神奇的事情发生了。

  3. 在第二次遍历fib-seq(ie (take 20 fib-seq)) 时,它的前几个元素已经被评估,因此do不会重新评估特殊形式,并且遍历继续进行而没有副作用。


要在从and中提取新元素时打印R并打印,您必须这样做:B(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)))))
于 2014-07-02T06:28:27.330 回答
0

感谢@omiel 提供了很多有用的信息,但还是没有触及到最敏感的地方,想了一会儿,我弄清楚了懒惰序列的生成发生了什么。
如果我clojure master真的错了,请纠正我。

一步一步的意思其实就是一个一个地关注惰性序列项的生成,我已经知道了一些clojure语言的逻辑。

正如我们所知道的,它fib-seq被定义为 alazy-seq并且它的前两项是 0 和 1,它的其余项仍然没有被计算,这是 clojure 最有趣的特性。
虽然很容易理解,访问前两项只是触摸这两项,它们在内存中或缓存中,因此可以直接返回并打印出来。

由于fib-seq目前没有第三项,它需要在线程需要访问第三项时生成它,这是我的假设开始的地方:

由于(map + (rest fib-seq) fib-seq )它本身是一个lazy-seq,因此它当前不包含任何项目并等待对其调用more命令。
这里调用第 3 项fib-seq意味着调用惰性序列的第一项(map...),因此需要生成并真正执行代码。
通过简单地将变量名替换为列表,map 的代码如下所示:

(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现在让我们触摸by的第 4 项(nth fib-seq 4)
fib-seq 发现它不包含带有 index 的项目4,但它发现第三个被缓存,因此它将从一个生成4th项目。3rd

现在线程移动到(map ...)函数并指示 map 分发它的第二项。
map 发现它没有 No.2 项目,所以它必须生成它。并替换 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,它只需移动两个列表并将它们一一添加并递归生成所需的斐波那契数,如下所示:

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

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

概括

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

如果我错了,请纠正我,我是新手,clojure我急于学习它。

顺便提一句

我想写一个集成,netbeans language plugin因为我认为没用,有人有什么建议吗?clojureleiningenlighttable

于 2014-07-02T15:12:34.507 回答