diff options
| author | eltrac <hi@eltr.ac> | 2026-09-29 18:39:27 +0800 |
|---|---|---|
| committer | eltrac <hi@eltr.ac> | 2026-09-29 18:39:27 +0800 |
| commit | 3a6c160f92afa78a3368d503c01fe9471e3e05b5 (patch) | |
| tree | ecf60da953d0741c334bc2024dd497a93764ddd4 | |
| parent | 15b1ed447918588d85931cf8d05abebbb5636de9 (diff) | |
[draft] update
| -rw-r--r-- | content/article/clj4.md | 69 |
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 会帮我们解决。 + + +{.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`。 + +") +{.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 语言中 `{}` 包裹的块。 |
