November 21, 2019
By: Kevin
理解Clojure的LazySeq
懒
"懒"是clojure的重要特性, 简洁表达意图的同时可以避免付出性能代价.
而且我们可以借助它实现无限序列, 做到按需求值.
;; 打开下面的注释会卡死当前页面
;; (range)
;; 只取无限序列的10个元素.
(take 10 (range))
好吧,骗你的,其实不会卡死. repl中eval代码的时候做限制了. 在lumo的repl环境中(本blog), 只会输出头1000个, 如果你拿到emacs的cider去试的话, 会发现cider的缓冲区不能大于1M,(range) 会执行输出到14万的时候求值停止。
懒序列在repl会被求值, 因为print的环节需要给用户输出. repl的设计过程中又不希望因为用户偶然输入(比如range忘了传参数)导致的自己卡死, 一旦发现某个语句快的返回值是lazy的时候,repl会比较节制的求值.
懒到不会动
但是在repl环境下, 会造成懒序列会执行的错觉. 而实际的代码执行的时候没有repl去强迫求值. 比如下面的情况:
程序会判断map不需要执行, 因为do表达式会抛弃它的执行结果, 也就是说没人对这个lazyseq求值.
(do (map prn [0 1 2 3 4 5])
(prn "我前面应该有一堆输出!"))
勤快到止不住
使用懒序列来进行纯粹计算的时候, 结果是符合预期的, 但是依赖它来执行一些副作用的时候, 就要小心了.
(prn "我下面应该有5条输出....对吧?")
(take 5 (map prn (range 10)))
懒序列执行的时候,是按32这个批次来执行的(一个性能优化).
(take 5 (map prn (range 100)))
避免使用懒序列来做副作用操作!!!!
咱们的代码规范中有相应条目:避免依赖lazyseq的副作用
正确的做法:
- 避免使用
map,reduce,reduce-kv,filter,take-while...等等返回lazyseq的高阶函数都不应该有什么副作用. - 需要副作用的时候考虑
doseqdorun- transducer, 这个值得再开主题讨论
杀死本tab的彩蛋
;; (prn (range))
(prn "这个真的会卡死, 它绕开了repl对返回值的类型的检测, 所以不会刹车")