讲师中心 微信公众号
AI工具推荐 视频效率加速

Transducer:强大的函数组合模式

浅浩君_5928

浅浩君_5928

发布时间:2025-01-13 14:33:34

|

328人浏览过

|

来源于php中文网

原创

transducer:强大的函数组合模式

别名:: transducer:强大的函数组合模式 笔记本:: transducer: 一种强大的函数组合模式

地图和过滤器

map 的作用是对集合中的每个元素应用一个转换函数。

<code class="javascript">const list = [1, 2, 3, 4, 5];

list.map(x => x + 1);
// [2, 3, 4, 5, 6]</code>

为了更清晰地展示 map 的实现,我们使用一个 for 循环:

<code class="javascript">function map(f, xs) {
  const ret = [];
  for (let i = 0; i < xs.length; i++) {
    ret.push(f(xs[i]));
  }
  return ret;
}

map(x => x + 1, [1, 2, 3, 4, 5]);
// [2, 3, 4, 5, 6]</code>

这段代码明确说明了 map 的实现依赖于数组的特性:顺序执行,立即求值。

接下来看看 filter:

<code class="javascript">function filter(f, xs) {
  const ret = [];
  for (let i = 0; i < xs.length; i++) {
    if (f(xs[i])) {
      ret.push(xs[i]);
    }
  }
  return ret;
}

const range = n => [...Array(n).keys()];

filter(x => x % 2 === 1, range(10));
// [1, 3, 5, 7, 9]</code>

同样,filter 也依赖于数组类型。

如何让 map 等函数支持不同数据类型,例如集合、Map 和自定义类型?一种通用的方法是依赖集合的接口(协议)。不同语言的实现方式不同。JavaScript 原生支持较弱,但可以通过 Symbol.iterator 进行迭代和使用 Object.constructor 获取构造函数来实现。

为了更灵活地处理不同数据类型,我们可以模仿 RamdaJS 库,使用自定义的 @@transducer/step 函数:

<code class="javascript">function map(f, xs) {
  const ret = new xs.constructor(); // 1. 构造函数
  for (const x of xs) { // 2. 迭代
    ret['@@transducer/step'](f(x)); // 3. 收集
  }
  return ret;
}

Array.prototype['@@transducer/step'] = Array.prototype.push;
// [function: push]

map(x => x + 1, [1, 2, 3, 4, 5]);
// [2, 3, 4, 5, 6]

Set.prototype['@@transducer/step'] = Set.prototype.add;
// [function: add]

map(x => x + 1, new Set([1, 2, 3, 4, 5]));
// Set(5) { 2, 3, 4, 5, 6 }</code>

通过这种方式,我们实现了更通用的 map 函数。关键在于将构造、迭代和收集操作委托给具体的集合类型,因为只有集合本身才知道如何执行这些操作。

类似地,我们可以实现一个更通用的 filter 函数:

<code class="javascript">function filter(f, xs) {
  const ret = new xs.constructor();
  for (const x of xs) {
    if (f(x)) {
      ret['@@transducer/step'](x);
    }
  }
  return ret;
}

filter(x => x % 2 === 1, range(10));
// [1, 3, 5, 7, 9]

filter(x => x > 3, new Set(range(10)));
// Set(6) { 4, 5, 6, 7, 8, 9 }</code>

组合

将 map 和 filter 组合使用时,会出现一些问题:

<code class="javascript">range(10)
  .map(x => x + 1)
  .filter(x => x % 2 === 1)
  .slice(0, 3);
// [1, 3, 5]</code>

尽管只使用了前三个元素,但整个集合都被遍历了,并且生成了多个中间集合对象。为了解决这个问题,我们使用 compose 函数:

<code class="javascript">function compose(...fns) {
  return fns.reduceRight((acc, fn) => x => fn(acc(x)), x => x);
}</code>

为了支持组合,我们将 map 和 filter 函数进行柯里化:

<code class="javascript">function curry(f) {
  return (...args) => data => f(...args, data);
}

const rmap = curry(map);
const rfilter = curry(filter);</code>

我们还需要一个 take 函数:

<code class="javascript">function take(n, xs) {
  const ret = new xs.constructor();
  for (const x of xs) {
    if (n-- > 0) {
      ret['@@transducer/step'](x);
    }
  }
  return ret;
}

take(3, range(10));
// [0, 1, 2]

take(4, new Set(range(10)));
// Set(4) { 0, 1, 2, 3 }</code>

现在我们可以组合这些函数了:

<code class="javascript">const rtake = curry(take);

const takefirst3odd = compose(
  rtake(3),
  rfilter(x => x % 2 === 1),
  rmap(x => x + 1)
);

takefirst3odd(range(10));
// [1, 3, 5]</code>

虽然代码清晰简洁,但运行效率仍然不高。

函数类型

Transducer

柯里化后的 map 函数的类型如下:

<code class="typescript">const map = f => xs => ...</code>

它返回一个单参数函数,这个函数就是一个 transformer:

<code class="typescript">type transformer = (xs: T) => R;</code>

transformer 方便函数组合。它的输入是数据,输出是处理后的数据。

<code class="typescript">data -> map(...) -> filter(...) -> reduce(...) -> result</code>

我们可以使用 pipe 函数来组合 transformer:

Add Typescript Best Practices
Add Typescript Best Practices

在 CLAUDE.md 中配置 TypeScript 最佳实践和代码风格规则

下载
<code class="javascript">function pipe(...fns) {
  return x => fns.reduce((ac, f) => f(ac), x);
}

const reduce = (f, init) => xs => xs.reduce(f, init);

const f = pipe(
  rmap(x => x + 1),
  rfilter(x => x % 2 === 1),
  rtake(5),
  reduce((a, b) => a + b, 0)
);

f(range(100));
// 25</code>

transformer 是单参数函数,方便组合。

Reducer

reducer 是一个双参数函数,可以表达更复杂的逻辑:

<code class="typescript">type reducer = (ac: R, x: T) => R;</code>

加法

<code class="javascript">// add is an reducer
const add = (a, b) => a + b;
const sum = xs => xs.reduce(add, 0);

sum(range(11));
// 55</code>

Map

<code class="javascript">function concat(list, x) {
  list.push(x);
  return list;
}

const map = f => xs => xs.reduce((ac, x) => concat(ac, f(x)), []);

map(x => x * 2)(range(10));
// [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]</code>

Filter

<code class="javascript">const filter = f => xs => xs.reduce((ac, x) => f(x) ? concat(ac, x) : ac, []);

filter(x => x > 3 && x < 7, range(10));
// [4, 5, 6]</code>

Take

实现 take 需要 reduce 函数具有类似 break 的功能:

<code class="javascript">function reduced(x) {
  return x && x['@@transducer/reduced'] ? x : { '@@transducer/reduced': true, '@@transducer/value': x };
}

function reduce(f, init) {
  return xs => {
    let ac = init;
    for (const x of xs) {
      const r = f(ac, x);
      if (r && r['@@transducer/reduced']) {
        return r['@@transducer/value'];
      }
      ac = r;
    }
    return ac;
  };
}

function take(n) {
  return xs => {
    let i = 0;
    return reduce((ac, x) => {
      if (i === n) {
        return reduced(ac);
      }
      i++;
      return concat(ac, x);
    }, [])(xs);
  };
}

take(4)(range(10));
// [0, 1, 2, 3]</code>

Transducer

让我们重新审视 map 函数:

<code class="javascript">function map(f, xs) {
  const ret = [];
  for (let i = 0; i < xs.length; i++) {
    ret.push(f(xs[i]));
  }
  return ret;
}</code>

我们需要将依赖数组的逻辑抽象成一个 reducer:

<code class="javascript">function rmap(f) {
  return reducer => {
    return (ac, x) => {
      return reducer(ac, f(x));
    };
  };
}</code>

构造、迭代和收集操作都消失了。rmap 只包含其核心逻辑。

类似地,我们可以实现 rfilter:

<code class="javascript">function rfilter(f) {
  return reducer => (ac, x) => {
    return f(x) ? reducer(ac, x) : ac;
  };
}</code>

注意 rfilter 和 rmap 的返回类型:

<code class="typescript">reducer => (acc, x) => ...</code>

它是一个 transducer,参数和返回值都是 reducer。transducer 是可组合的。

应用 Transducer

如何使用 transducer?

<code class="javascript">const compose = (...fns) => fns.reduceRight((acc, fn) => x => fn(acc(x)), x => x);

const tf = compose(
  rmap(x => x + 1),
  rfilter(x => x % 2 === 1),
  rtake(5)
);

const collect = (ac, x) => {
  ac.push(x);
  return ac;
};

const reducer = tf(collect);
reduce(reducer, [])(range(100));
// [1, 3, 5, 7, 9]</code>

我们将逻辑封装成一个函数:

<code class="javascript">function into(init, tf) {
  const reducer = tf(collect);
  return reduce(reducer, init);
}

into([], compose(
  rmap(x => x + 1),
  rfilter(x => x % 2 === 1),
  rtake(8)
))(range(100));
// [1, 3, 5, 7, 9, 11, 13, 15]</code>

迭代是按需的。

异步数据流

考虑一个异步的斐波那契数列生成器:

<code class="javascript">function sleep(n) {
  return new Promise(r => setTimeout(r, n));
}

async function* fibs() {
  let [a, b] = [0, 1];
  while (true) {
    await sleep(10);
    yield a;
    ;[a, b] = [b, a + b];
  }
}

const s = fibs();
async function start() {
  let i = 0;
  for await (const item of s) {
    console.log(item);
    i++;
    if (i > 10) {
      break;
    }
  }
}

start();</code>

我们需要修改 into 函数以支持异步迭代器:

<code class="javascript">const collect = (ac, x) => {
  ac.push(x);
  return ac;
};

const reduce = (reducer, init) => {
  return async iter => {
    let ac = init;
    for await (const item of iter) {
      if (ac && ac['@@transducer/reduced']) {
        return ac['@@transducer/value'];
      }
      ac = reducer(ac, item);
    }
    return ac;
  };
};

function sinto(init, tf) {
  const reducer = tf(collect);
  return reduce(reducer, init);
}

const task = sinto([], compose(
  rmap(x => x + 1),
  rfilter(x => x % 2 === 1),
  rtake(8)
));

task(fibs()).then(res => {
  console.log(res);
});</code>

相同的逻辑适用于不同的数据结构。

执行顺序

基于柯里化的 compose 和基于 reducer 的 compose 的参数顺序不同。

柯里化版本

函数执行是右关联的。

Transducer 版本

参考

Transducer - Clojure 参考

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2811

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

534

2025.10.31

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

482

2026.02.12

java中break的作用
java中break的作用

本专题整合了java中break的用法教程,阅读专题下面的文章了解更多详细内容。

2580

2025.10.15

java break和continue
java break和continue

本专题整合了java break和continue的区别相关内容,阅读专题下面的文章了解更多详细内容。

747

2025.10.24

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2441

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

356

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

377

2026.01.06

PixTV官网入口地址合集
PixTV官网入口地址合集

本专题汇总了 PixTV AI 一站式视频创作平台的官方入口与使用教程。无需下载软件,浏览器直接访问即可使用。平台将剧本、图像、视频、声音与剪辑整合在“无限画布”中,接入 GPT Image 2.5、Seedance 2.5 等头部模型。本专题整理了从新建画布、角色锚定、分镜拆分到视频生成与导出的完整操作指南,助你快速上手 AI 短剧与漫剧创作。

80

2026.10.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.6万人学习

TypeScript——十天技能课堂
TypeScript——十天技能课堂

共21课时 | 1.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn