Clojure:Transducers

Hacker News Top 工具

摘要

Clojure 官方文档解读 transducers——可组合、带状态的转换函数,将序列处理与具体集合类型解耦。

暂无内容
查看原文 导出为 Word 导出为 PDF
查看缓存全文

缓存时间: 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

Lobsters Hottest

jank 是一种 Clojure 方言,现已引入一种在 Clojure 语义层面设计的自定义中间表示,以实现更好的优化并与 JVM 竞争。

ClojureScript 迎来 Async/Await

Hacker News Top

ClojureScript 1.12.145 通过 ^:async 提示引入原生异步函数支持,实现与 JavaScript async/await 的直接互操作,无需额外依赖。

Transformer 数学探索器 [P]

Reddit r/MachineLearning

这个交互式工具通过数据流图可视化 Transformer 模型的数学基础,涵盖了从 GPT-2 到 Qwen 3.6 的架构以及各种注意力机制。