代码在这里:
(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)
我再也不会收到R
和B
,这让我感到困惑,因为它与我对懒惰序列生成的理解相矛盾。
能不能一步一步地向我解释?
顺便说一句,有没有可能有一个debug
实用程序来调试它step by step
就像Java
和C
一样?
答案 0 :(得分:3)
好的,一步一步:
以下是lazy-cat
(link)的源代码:
(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
表单的正文。
当您第一次遍历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
特殊表单会被评估,并且您会看到R
和B
已打印出来。 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
的懒惰会阻止无限递归的发生,并且会发生魔法。
在第二次遍历fib-seq
(即(take 20 fib-seq)
)时,其前几个元素已经过评估,因此不会重新评估do
特殊表单并且遍历继续没有副作用。
要在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)))))
答案 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
clojure
与leiningen
整合,因为我认为lighttable
没用,有人有任何建议吗?