如标题
请问下这个lazy-seq怎么用
netseiner
#3
(defn fib
([] (fib 1 1))
([a b]
(lazy-seq (cons a
(fib b (+ a b))))))
那这个呢
(take 5 (fib)) ;;(1,1,2,3,5)
lins05
#6
其实 @miaotou 的回答已经够了,只是 你给的这个例子其实是被三个概念 (cons, lazy-seq, 递归) 混在一起搞复杂化了,所以不好理解。 cons 和 lazy-seq 其实都很直观,但是这里 fib 是一个递归函数,把整个代码变复杂了 N 倍。
- cons: 如果你学过链表的话,
(cons x y)
从概念上相当于制造一个链表,表头是 x, next 指针就是指向 y - lazy-seq: (lazy-seq body) 会返回一个 lazy seq, 只有在遍历它时(比如对它调用 count/reduce 等函数时) 才会执行 body 来得到它包含的元素。所以 lazy-seq 的实现是一个 macro, 因为只有 macro 才能做到被调用时不先把参数求值。
你这个例子中 lazy-seq 的 body 是 (cons a (fib b (+ a b))
, 所以:
- cons 的链表头是一个明确的数字
- 而 cons 的 next 指针部分又是一个对 fib 的调用, 所以它又是一个 lazy-seq+cons, 表头是 (a + b) …
- 这样,所以整个 fib 函数返回的 lazy-seq 展开后的规律就是 (1,1, 2=1+1, 3=1+2)
你可以在函数体合适的地方加入 println 语句打印当前 a 和 b 的值来理解。
(defn fib
([] (fib 1 1))
([a b]
(println (str "a = " a ", b = " b))
(lazy-seq (cons a
(fib b (+ a b))))))
(def foo (doall (take 5 ( fib))))
; a = 1, b = 1
; a = 1, b = 2
; a = 2, b = 3
; a = 3, b = 5
; a = 5, b = 8
; a = 8, b = 13
要想更多的了解 lazy 的话,其它语言中也有 lazy 的概念:
- python 中的 generator 几乎和 lazy seq 是一样的。
- scala 中有 lazy val 的概念, “lazy val x = y * 1000” 这样的写法,是第一次使用 x 时才执行
y * 1000
这个计算,并且把结果缓存起来,以后每次用到 x 就不用再计算了。本质上只是一个语法糖。
clojure 中的 lazy seq 是最强大的,也是最不好理解和最容易被坑的。我觉得 lazy seq 的好处主要有两个,一是节省开销用到时再计算,用不到时就不计算。二是可以表达“无限序列”这样的数学上的概念直观地在代码中表达出来,好看又实用,典型的例子就是标准库中的 iterate, repeat, cycle 等等。