# 转换器 转换器(transducer)是一种可复用的转换管道,独立于数据源或汇点。转换器让你只需编写一次“map + filter + take”部分,就能将其应用于任何集合、归约函数或通道。 转换器是有状态函数:`(rfn) → rfn'`。它们可以组合、提前终止,并且不会分配中间集合。 ## 快速开始
# A transducer is built by composing 1-arg forms
def xf [comp
    [map {|x| ($x * 2)}]
    [filter is-even]
    [take 10]]

# Apply it wherever a reducing context is expected
[into @{} xf $xs]                     # conj into a list
[into ^{} xf $xs]                     # add into a set
[transduce xf + 0 $xs]                # fold via a plain fn
[transduce xf [completing +] 0 $xs]   # explicit Reducer wrap
## 两种形式 `map`、`filter`、`take`、`drop` 和 `mapcat` 是按参数个数重载的: - 单参数返回转换器:`[map f]`、`[filter pred?]`、`[take n]` - 双参数返回惰性序列:`[map $xs f]`、`[filter $xs pred?]` 惰性序列形式在惰性序列中介绍。转换器是用 `comp` 组合的单参数形式。 ## 组合转换器 `comp` 从左到右组合转换器(数据按声明顺序流经它们):
def xf [comp
    [filter { ($it % 2) == 0 }]
    [map { $it + 1 }]
    [take 3]]

[into @{} xf @{ 1 2 3 4 5 6 7 8 }]
# => @{ 3 5 7 }
注意,`comp` 按源顺序执行 —— `filter` 在 `map` 之前、`map` 在 `take` 之前 —— 这与数学上的函数复合不同。 ## 应用转换器 ### `into` — 将元素 conj 进集合 `[into target xf source]` 让 `source` 流经 `xf`,将每个结果元素 conj 到 `target` 中。target 可以是列表字面量、集合字面量、映射字面量,或任何现有集合:
[into @{} xf $xs]                     # list
[into ^{} xf $xs]                     # set (duplicates collapse)
### `transduce` — 使用自定义归约函数折叠
[transduce xf + 0 $xs]                # sum of transformed elements
[transduce xf str "" $xs]             # concatenate
归约函数可以是普通的双参数可调用对象(通过 `completing` 自动包装),也可以是 `%Reducer{:init :step :done}` 标签映射。 ### `%Reducer` — 完整的归约协议 归约函数是 `%Reducer{:init :step :done}` 标签映射。`:init` 和 `:done` 键可以为 `nil`。`completing` 将普通的双参数函数包装为 `%Reducer`:
def sum-reducer %Reducer{
    :init  {|| 0}
    :step  {|acc x| ($acc + $x)}
    :done  {|result| [println "sum was $result"] }
}

[transduce xf $sum-reducer 0 $xs]
## 提前终止 `(take n)` 会在任何归约上下文中短路 —— 不仅是惰性序列。底层使用 `%Reduced`:
# A transducer chain with take inside
def xf [comp [map { $it * 2 }] [take 3]]

# Even when transducing into a sum, the chain stops after 3 elements
[transduce xf + 0 @{ 1 2 3 4 5 6 7 8 9 10 }]
# => 12 (only 1,2,3 were processed: 2 + 4 + 6)
### `reduced` / `reduced?` / `unreduced` 构建你自己的提前终止步骤:
def first-even {|
    set step {|acc x|
        if (is-even x) {
            [reduced x]                       # wrap to terminate
        } {
            $acc
        }
    |
    }
    %Reducer{ :init {|| nil} :step $step :done nil }
|
}

[transduce [filter identity] $first-even nil @{ 1 3 5 6 7 }]
# => 6

[reduced? [reduced 42]]                      # => true
[unreduced [reduced 42]]                     # => 42
[unreduced 42]                                # => 42 (passthrough)
## 惰性序列 vs 转换器 | 方面 | 惰性序列 | 转换器 | |---|---|---| | 数据源 | 始终是集合 | 任何归约上下文 | | 汇点 | 始终是列表(通过 `collect`) | 列表、集合、映射、求和、自定义 | | 复用 | 每次调用 | 可跨集合复用 | | 提前终止 | 链中的 `take n` | 任意步骤中的 `take n` 或 `%Reduced` | | 最适合 | 一次性、输出列表、可读性佳 | 可复用、多汇点、性能关键 | ## 何时使用转换器 - 同一条转换管道要应用于多个集合或汇点。 - 汇点不是向量(集合、映射、求和、自定义归约函数)。 - 你希望整条链上显式提前终止。 - 你要向通过归约函数消费的通道或异步任务提供数据。 对于一次性、集合到集合的转换,若可读性最重要,优先使用 `|>` 管道(参见惰性序列)。 ## 示例 ### 前 10 个偶数的平方和
def xf [comp
    [filter { ($it % 2) == 0 }]
    [map { $it * $it }]
    [take 10]]

[transduce xf + 0 [range 0 1000]]
# => 0 + 4 + 16 + ... + 36 (= sum of squares of 0,2,4,...,18)
### 将列表索引为映射
def index-by-name [comp
    [map {|x| %{ :key $x~name :val $x }}]
    (mapcat {|kv| @{ $kv }})])               # unfold into key/val pairs

[into ^{} index-by-name $users]              # set of users by name
### 与多个数据源组合
def normalize [comp [map trim] [filter not-empty?] [map lower]]

[into @{} normalize $raw-lines]              # one source
[into @{} normalize $other-lines]            # different source, same pipeline
## 快速参考 | 形式 | 用途 | |---|---| | `[map f]` | 单参数转换器 | | `[filter pred?]` | 单参数转换器 | | `[take n]` / `[drop n]` | 单参数转换器 | | `[mapcat f]` | 单参数转换器 | | `[comp xf1 xf2 ...]` | 组合转换器 | | `[into target xf source]` | conj 到集合中 | | `[transduce xf rfn init source]` | 通过归约函数折叠 | | `[completing f]` | 将普通函数包装为 `%Reducer` | | `[reduced x]` / `[reduced? x]` / `[unreduced x]` | 提前终止协议 |