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 值,则待处理状态应直接丢弃。
相似文章
jank 现已拥有自己的自定义 IR
jank 是一种 Clojure 方言,现已引入一种在 Clojure 语义层面设计的自定义中间表示,以实现更好的优化并与 JVM 竞争。
ClojureScript 迎来 Async/Await
ClojureScript 1.12.145 通过 ^:async 提示引入原生异步函数支持,实现与 JavaScript async/await 的直接互操作,无需额外依赖。
Show HN: Ctx – 跨 Claude Code 与 Codex 都能用的 /resume
Ctx 是一款本地优先的上下文管理器,开发者可用它在 Claude Code 和 Codex 之间绑定、恢复并分叉精确对话,杜绝记录漂移,依托 SQLite 与本地文件。
@simplifyinAI: DeepSeek 对 Transformer 架构进行了根本性重构。它解决了导致大规模 AI 模型崩溃的“身份危机”……
DeepSeek 发表了一篇论文,介绍了 mHC(流形约束超连接,Manifold-Constrained Hyper-Connections),这是一种对 Transformer 架构的根本性重写,通过用数学约束的多流路径替换标准残差连接,来稳定大型模型。
Transformer 数学探索器 [P]
这个交互式工具通过数据流图可视化 Transformer 模型的数学基础,涵盖了从 GPT-2 到 Qwen 3.6 的架构以及各种注意力机制。