summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-09-29 18:39:27 +0800
committereltrac <hi@eltr.ac>2026-09-29 18:39:27 +0800
commit3a6c160f92afa78a3368d503c01fe9471e3e05b5 (patch)
treeecf60da953d0741c334bc2024dd497a93764ddd4
parent15b1ed447918588d85931cf8d05abebbb5636de9 (diff)
[draft] update
-rw-r--r--content/article/clj4.md69
1 files changed, 67 insertions, 2 deletions
diff --git a/content/article/clj4.md b/content/article/clj4.md
index b5637a13..396d539d 100644
--- a/content/article/clj4.md
+++ b/content/article/clj4.md
@@ -109,7 +109,7 @@ addWithCounter(numbers, 0, 0, 0)
;; => {:counter 3, :total 6}
```
-不过更 *Idiomatic* 的写法是:
+不过更好的写法是用利用惰性。
```clojure
;; reductions 类似于 reduce
@@ -128,7 +128,72 @@ addWithCounter(numbers, 0, 0, 0)
## 初识惰性
-## Footnotes
+`lazy-seq` 函数接收一系列表达式,这些表达式最终返回一个序列。不过,写在 `lazy-seq` 里面的表达式并不会被立即执行,而是会在 `lazy-seq` 被访问时执行。也就是说,`lazy-seq` 保存了一系列「如何求值」的步骤,仅在每个步骤被访问时才计算具体的数值。
+
+还记得序列(sequence)的定义吗?仅支持线性访问的集合。还记得 Cons 吗?一个序列就是保存了第一个值(`_first`)和指向下一个 Cons 的“指针”(`_next`)的对象。假如序列的 `next` 并不指向下一个 Cons 的,调用它的 `next()` 方法时,它并不 `return _next`,而是调用函数计算下一个值再返回,调用几次 `next()` 就计算几次值。
+
+听起来像个迭代器(Iterator),两者确实很像。惰性序列就是可以当作序列处理的迭代器,不过我们并不需要手动调用 `next`,只需要正常地把它当作序列处理,剩下的 Clojure 会帮我们解决。
+
+![](https://img.eltr.ac/geedeapro/article/clj4/lazy-seq.jpg "对一个 Lazy Seq 分别调用一次 first 和两次 next 函数的示意图")
+{.dark:invert}
+
+作为演示,我们先定义一个指向无穷的符号。
+
+```clojure
+(def ∞ (range))
+;; => #'user/∞
+
+(type ∞)
+;; => clojure.lang.Iterate
+```
+
+`range` 可以返回指定范围的整数序列,如果不给参数的话,就会返回 `Iterate` 对象。可以把 `Iterate` 理解为做了特殊优化的 `LazySeq`,使用起来和惰性序列区别不大。
+
+我们试试从「无穷」中取出前五个数:
+
+```clojure
+(take 5 ∞)
+;; => (0 1 2 3 4)
+```
+
+只想要正整数?没问题。
+
+```clojure
+(def ℤ (map inc ∞))
+;; => #'user/ℤ
+
+(take 5 ℤ)
+;; => (1 2 3 4 5)
+```
+
+只想要偶数?当然!
+
+```clojure
+;; 其实我本来想用 {2k|k∈ℤ},可惜不合法
+(def even-numbers (filter even? ℤ))
+;; => #'user/even-numbers
+
+(take 5 even-numbers)
+;; => (2 4 6 8 10)
+```
+
+但要注意,有些操作不能应用在无穷的惰性序列上,比如 `shuffle`,这个函数的作用是打乱一个序列,但你怎么打乱「无穷」呢?想想就需要无穷的计算时间。此外还有 `sort`、`group-by` 和 `count`。
+
+![](https://img.eltr.ac/geedeapro/article/clj4/laziness-they-have-no-idea.webp "图片来自:[Clojure's Deadly Sin](https://clojure-goes-fast.com/blog/clojures-deadly-sin/)")
+{.dark:invert}
+
+等等,`count`?难道之前不是说 `count` 的时间复杂度为 `O(1)`,只需要 `return _count` 就好了吗?对惰性序列来说不是这样,因为在把所有可能性都实现之前,惰性序列自己也不知道自己有多长。
+
+```java
+// package clojure.lang
+// LazySeq 的 count 方法
+public int count(){
+ int c = 0;
+ for(ISeq s = seq(); s != null; s = s.next())
+ ++c;
+ return c;
+}
+```
[^1]: 比如 C 语言中 `{}` 包裹的块。