From dcade465c001d0ae2f2f486683ca75ebe3a5aa40 Mon Sep 17 00:00:00 2001 From: eltrac Date: Sat, 17 Oct 2026 18:40:14 +0800 Subject: [draft] new clj8 --- content/article/clj8.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 content/article/clj8.md (limited to 'content') diff --git a/content/article/clj8.md b/content/article/clj8.md new file mode 100644 index 00000000..aa2442c1 --- /dev/null +++ b/content/article/clj8.md @@ -0,0 +1,100 @@ ++++ +title = 'Coffee Break Clojure, Fold' +subtitle = '' +slug = 'clj8' +tags = ["Clojure","Lisp","编程"] +categories = '茶歇蔻芥' +date = 2026-10-17T17:45:13+08:00 +draft = true +toc = true ++++ + +在第三期《[只用圆括号怎么够?](/article/clj3/#reduce)》里,我讲到了 `reduce` 函数,先来简单复习一下:`reduce` 最少接收两个参数,形如 `(reduce f coll)`,其中 `f` 是接收两个参数的函数,形如 `(f x y)`;`reduce` 遍历 `coll` 中的每个元素,现对前两个元素求 `(f coll1 coll2)`,然后对第三个参数求 `(f (f coll1 coll2) coll3)`,以此类推。 + +`(f x y)` 这种函数叫作归约函数(reducing function)。 + +## 归约、Fold、高阶函数 + +之前我们用作示例的代码如下: + +```clojure +(def numbers '(1 43 231 23 99)) + +(reduce + numbers) +;; => 397 +``` + +`+` 可以视作归约函数,因为他可以这样调用:`(+ x y)`。当我们对上述代码进行求值时,主要执行了以下步骤: + +1. `(+ 1 43)` 得到 `44` +2. `(+ 44 231)` 得到 `275` +3. `(+ 275 23)` 得到 `298` +4. `(+ 298 99)` 得到 `397` + +使用 `+` 作为归约函数,对 `numbers` 这个数字列表进行归约(reduce),就达到了累加的效果。在函数式编程里,归约(reduce)也叫作累积(accumulate)、聚集(accumulate)、压缩(compress)和注入(inject),不过更常见的名字是折叠(fold)。 + +还记得第三篇讲过的 [Cons](/article/clj3/#圆括号和列表) 和 Lisp 的列表构造方式吗?Fold 的结构就像是函数版本的 Cons。 + +
+ +
+ +```clojure +(reduce f [1 2 3 4 5]) +;; 等价于 +(f (f (f (f 1 2) 3) 4) 5) +``` + +
+ +
+ +```clojure +(list 1 2 3 4 5) +;; 等价于 +(cons 1 (cons 2 (cons 3 (cons 4 (cons 5 nil))))) +``` + +
+ +
+ +Fold 有方向,Clojure 的 `reduce` 默认是左折叠,即 `foldl`: + +![](https://img.eltr.ac/geedeapro/article/clj8/Left-fold-transformation.png "左折叠示意图;图源:[维基百科](https://zh.wikipedia.org/wiki/Fold_(%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0))") + +要实现右折叠很简单,只要把待操作的列表倒序就好: + +```clojure +(reduce f (reverse [1 2 3 4 5])) +;; 等价于 +(f (f (f (f 5 4) 3) 2) 1) +``` + +Fold 中的 `f` 本身也可以是 Fold。还记得 `into` 吗? + +```clojure +(into [1 2 3] [4 5]) +;; 等价于 +(reduce conj [1 2 3] [4 5]) ;; 这里的 reduce 以 [1 2 3] 为初始值 + ;; 如果没有初始值,就会用第一个元素做初始值 +;; 等价于 +(conj (conj (conj [1 2 3]) 4) 5) +``` + +如果要打平一个向量,比如把 `[[1] [2 3] [4 5]]` 变成 `[1 2 3 4 5]`,就可以用 `into` 作为归约函数(reducing function),也就是 Fold 中的 `f`。 + +```clojure +(reduce into [[1] [2 3] [4 5]]) +;; 等价于 +(reduce #(reduce conj %1 %2) [[1] [2 3] [4 5]]) +;; 等价于 +(reduce conj (reduce conj [1] [2 3]) [4 5]) +;; 等价于 +(conj (conj (conj (conj [1] 2) 3) 4) 5) +``` + +在 Clojure 中,能够被放在 `reduce` 里使用的函数就叫作归约函数(reducing function)。这个概念与函数式编程和 Lambda 演算的高阶函数(Higher-order function)有关,据说微积分中的导数也算一种高阶函数,但我这篇文章不是讲数学的(我也不会数学),所以就点到为止。 + + + -- cgit v1.2.3