Clojure:Transducers
摘要
Clojure 官方文档解读 transducers——可组合、带状态的转换函数,将序列处理与具体集合类型解耦。
暂无内容
查看缓存全文
缓存时间: 2026/04/21 16:01
# Clojure - Transducers
来源:https://clojure.org/reference/transducers
Transducer 具有如下外形(自定义代码写在 “...” 中):
``
(fn [rf]
(fn ([] ...)
([result] ...)
([result input] ...)))
``
绝大多数核心序列函数(如 map、filter 等)会先接收与操作相关的参数(谓词、函数、数量等),然后返回一个符合上述外形的 transducer,并在闭包中捕获这些参数。某些情况下,如 **cat**,核心函数本身*就是* transducer,不再额外接收 **rf** 参数。
内部函数定义了 3 个不同用途的元数:
- **Init**(0 元)——应调用嵌套转换 **rf** 的 init 元数,最终回调到整个 transducing 流程。
- **Step**(2 元)——标准的归约函数,但应根据需要 0 次或多次调用 **rf** 的 step 元数。例如,filter 会依据谓词决定是否调用 **rf**;map 总是恰好调用一次;cat 则可能调用多次。
- **Completion**(1 元)——某些流程永不结束,但对于会结束的流程(如 **transduce**),用该元数生成最终值和/或刷新状态。必须恰好调用一次 **rf** 的 completion 元数。
**completion** 的典型用例是 **partition-all**,它必须在输入结束时把剩余元素全部刷出。[completing](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/completing) 函数可为普通归约函数添加默认 completion 元数,从而将其转换成 transducing 函数。
### [提前终止](https://clojure.org/reference/transducers#_early_termination)
Clojure 提供了提前结束 reduce 的机制:
- [reduced](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/reduced)——接收一个值,返回一个 *reduced* 值,表示应停止归约。
- [reduced?](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/reduced?)——若值由 *reduced* 创建,则返回 true。
- [deref](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/deref) 或 @ 可取出 *reduced* 内的值。
使用 transducer 的流程必须在 step 函数返回 reduced 值时检测并停止(详见“创建可 Transduce 的流程”)。此外,若 transducer 的 step 函数内部又用了归约,也必须检测并传递遇到的 reduced 值(参见 cat 的实现)。
### [带归约状态的 Transducer](https://clojure.org/reference/transducers#_transducers_with_reduction_state)
某些 transducer(如 **take**、**partition-all** 等)在归约过程中需要维护状态。每次可 transduce 的流程应用该 transducer 时,都会重新创建这份状态。以去重 transducer dedupe 为例,它会将连续重复值压缩为单个值,因此必须记住“前一个值”:
``
(defn dedupe []
(fn [xf]
(let [prev (volatile! ::none)]
(fn
([] (xf))
([result] (xf result))
([result input]
(let [prior @prev]
(vreset! prev input)
(if (= prior input)
result
(xf result input))))))))
``
在 dedupe 中,**prev** 是有状态容器,存放上一次见到的值。出于性能考虑使用 volatile,也可以用 atom。直到 transducing 流程启动(例如调用 **transduce**)时,prev 才会被初始化,因此所有状态交互都被限制在该流程的上下文中。
在 completion 步骤中,带状态的 transducer 应先刷新状态,再调用嵌套转换器的 completion 函数;但如果之前已收到嵌套 step 返回的 reduced 值,则待处理状态应直接丢弃。
相似文章
@freeCodeCamp: Clojure 是一种函数式编程语言,能改变你对编写代码的思考方式。在这份互动指南…
一份互动指南,通过动画代码回放和动手练习介绍 Clojure 编程,涵盖函数、不可变数据、递归等。
依赖类型的 Clojure 领域特定语言,带有与 Lean4 兼容的内核
Ansatz 是一个 Clojure 库,实现了 Lean 4 的归纳构造演算内核,使 Clojure 程序员能够编写依赖类型的、经过验证的代码,这些代码的证明与 Mathlib 兼容,并能编译为 JVM 字节码。
用于分析的纯 Clojure 列式数据库
Flatiron 是一个纯 Clojure 的列式分析库,用于内存表,具有类似 SQL 的 DSL,专为使用原始数组和批处理的高性能而设计。
Clojure 速度几乎媲美 C(需借助一些优化)
本文详细介绍了 Clojure 如何借助 JVM 的 Vector API 和精心优化,在 3D 压力测试中达到接近 C 的帧率(仅差 20%),展示了动态语言在热循环中也能接近底层性能。
Transformer之药
对Transformer架构在大型语言模型之外广泛影响的反思,包括对语言学、遗传学和因果建模的潜在影响,并将其意义与哈伯-博世法相提并论。