alias:: Transducer:强大的函数组合模式
Notebook:: Transducer: 一个强大的函数组合模式
地图和过滤器
map 的语义是“映射”,意思是对集合中的所有元素执行一次转换。
const list = [1, 2, 3, 4, 5] list.map(x => x + 1) // [ 2, 3, 4, 5, 6 ]
function map(f, xs) { const ret = [] for (let i = 0; i <pre class="brush:php;toolbar:false"> map(x => x + 1, [1, 2, 3, 4, 5]) // [ 2, 3, 4, 5, 6 ]
上面故意用了一个for语句来明确表达map的实现依赖于集合类型。
顺序执行;
立即评价,不偷懒。
让我们看看过滤器:
function filter(f, xs) { const ret = [] for (let i = 0; i <pre class="brush:php;toolbar:false"> var range = n => [...Array(n).keys()]
filter(x => x % 2 === 1, range(10)) // [ 1, 3, 5, 7, 9 ]
同样,filter 的实现也取决于具体的集合类型,当前的实现要求 xs 为数组。
地图如何支持不同的数据类型?例如,集合、地图和自定义数据类型。
有一个常规的方式:依赖集合的接口(协议)。
不同的语言有不同的实现,JS在这方面原生支持相对较弱,但也是可行的:
使用 Symbol.iterator 进行迭代。
使用 Object#contractor 获取构造函数。
那么我们如何抽象地支持推送中的不同数据类型?
模仿ramdajs库,可以依赖自定义的@@transducer/step函数。
function map(f, xs) { const ret = new xs.constructor() // 1. construction for (const x of xs) { // 2. iteration ret['@@transducer/step'](f(x)) // 3. collection } 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}
通过该方法,我们可以实现地图、过滤等功能,更加轴向化。
关键是将构造、迭代、集合等操作委托给具体的集合类,因为只有集合本身知道如何完成这些操作。
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}
撰写
以上地图和过滤器结合使用时会出现一些问题。
range(10) .map(x => x + 1) .filter(x => x % 2 === 1) .slice(0, 3) // [ 1, 3, 5 ]
虽然只使用了5个元素,但是集合中的所有元素都会被遍历。
每一步都会生成一个中间集合对象。
我们再次使用 compose 来实现这个逻辑
function compose(...fns) { return fns.reduceRight((acc, fn) => x => fn(acc(x)), x => x) }
为了支持组合,我们以 curry 的形式实现了 map 和 filter 等功能。
function curry(f) { return (...args) => data => f(...args, data) }
var rmap = curry(map) var rfilter = curry(filter) function take(n, xs) { const ret = new xs.constructor() for (const x of xs) { if (n <pre class="brush:php;toolbar:false"> take(3, range(10)) // [ 0, 1, 2 ]
take(4, new Set(range(10))) // Set (4) {0, 1, 2, 3}
const takeFirst3Odd = compose( rtake(3), rfilter(x => x % 2 === 1), rmap(x => x + 1) ) takeFirst3Odd(range(10)) // [ 1, 3, 5 ]
到目前为止,我们的实现在表达上是清晰简洁的,但在运行时却很浪费。
函数的形状
变压器
咖喱版本中的地图功能是这样的:
const map = f => xs => ...
也就是说,map(x => ...) 返回一个单参数函数。
const list = [1, 2, 3, 4, 5] list.map(x => x + 1) // [ 2, 3, 4, 5, 6 ]
可以轻松组合具有单个参数的函数。
具体来说,这些函数的输入是“数据”,输出是处理后的数据,函数就是一个数据转换器(Transformer)。
function map(f, xs) { const ret = [] for (let i = 0; i <pre class="brush:php;toolbar:false"> map(x => x + 1, [1, 2, 3, 4, 5]) // [ 2, 3, 4, 5, 6 ]
function filter(f, xs) { const ret = [] for (let i = 0; i <p>Transformer 是单参数函数,方便函数组合。<br> </p> <pre class="brush:php;toolbar:false"> var range = n => [...Array(n).keys()]
减速器
reducer 是一个二参数函数,可用于表达更复杂的逻辑。
filter(x => x % 2 === 1, range(10)) // [ 1, 3, 5, 7, 9 ]
和
function map(f, xs) { const ret = new xs.constructor() // 1. construction for (const x of xs) { // 2. iteration ret['@@transducer/step'](f(x)) // 3. collection } 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]
拿
如何实施take?这需要reduce具有类似于break的功能。
map(x => x + 1, new Set([1, 2, 3, 4, 5])) // Set (5) {2, 3, 4, 5, 6}
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}
我们需要找到一种方法,将上面提到的依赖于数组(Array)的逻辑分离出来,抽象成一个Reducer。
range(10) .map(x => x + 1) .filter(x => x % 2 === 1) .slice(0, 3) // [ 1, 3, 5 ]
构造消失了,迭代消失了,元素的集合也消失了。
通过减速器,我们的映射仅包含其职责范围内的逻辑。
再看看过滤器
function compose(...fns) { return fns.reduceRight((acc, fn) => x => fn(acc(x)), x => x) }
注意上面的 rfilter 和 rmap 的返回类型:
function curry(f) { return (...args) => data => f(...args, data) }
它其实是一个Transfomer,参数和返回值都是Reducer,它就是Transducer。
Transformer 是可组合的,因此 Transducer 也是可组合的。
var rmap = curry(map) var rfilter = curry(filter) function take(n, xs) { const ret = new xs.constructor() for (const x of xs) { if (n <h2> 进入并转换 </h2> <p>但是,如何使用换能器?<br> </p> <pre class="brush:php;toolbar:false"> take(3, range(10)) // [ 0, 1, 2 ]
take(4, new Set(range(10))) // Set (4) {0, 1, 2, 3}
我们需要使用reducer来实现迭代和收集。
const takeFirst3Odd = compose( rtake(3), rfilter(x => x % 2 === 1), rmap(x => x + 1) ) takeFirst3Odd(range(10)) // [ 1, 3, 5 ]
现在可以工作了,我们还注意到迭代是“按需”的。虽然集合中有 100 个元素,但只迭代了前 10 个元素。
接下来我们将上面的逻辑封装成一个函数。
const map = f => xs => ...
type Transformer = (xs: T) => R
流动
斐波那契发生器。
假设我们有某种异步数据收集,例如异步无限斐波那契生成器。
data ->> map(...) ->> filter(...) ->> reduce(...) -> result
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
我们需要实现支持上述数据结构的 into 函数。
把数组版本的代码贴在旁边作为参考:
type Transformer = (x: T) => T
这是我们的实现代码:
type Reducer = (ac: R, x: T) => R
集合操作相同,迭代操作不同。
// add is an reducer const add = (a, b) => a + b const sum = xs => xs.reduce(add, 0) sum(range(11)) // 55
相同的逻辑适用于不同的数据结构。
订单
细心的你可能会注意到,基于curry的compose版本和基于reducer的版本参数顺序是不同的。
咖喱版
const list = [1, 2, 3, 4, 5] list.map(x => x + 1) // [ 2, 3, 4, 5, 6 ]
function map(f, xs) { const ret = [] for (let i = 0; i函数的执行是右关联的。
传感器版本
map(x => x + 1, [1, 2, 3, 4, 5]) // [ 2, 3, 4, 5, 6 ]function filter(f, xs) { const ret = [] for (let i = 0; i <h2> 参考 </h2> <p>换能器来了<br> 传感器 - Clojure 参考</p>
以上是Transducer:强大的函数组合模式的详细内容。更多信息请关注PHP中文网其他相关文章!

javascriptisnotbuiltoncorc; saninterpretedlanguagethatrunsonenginesoftenwritteninc.1)javascriptwasdesignedAsalightweight,解释edganguageforwebbrowsers.2)Enginesevolvedfromsimpleterterterpretpreterterterpretertestojitcompilerers,典型地提示。

JavaScript可用于前端和后端开发。前端通过DOM操作增强用户体验,后端通过Node.js处理服务器任务。1.前端示例:改变网页文本内容。2.后端示例:创建Node.js服务器。

选择Python还是JavaScript应基于职业发展、学习曲线和生态系统:1)职业发展:Python适合数据科学和后端开发,JavaScript适合前端和全栈开发。2)学习曲线:Python语法简洁,适合初学者;JavaScript语法灵活。3)生态系统:Python有丰富的科学计算库,JavaScript有强大的前端框架。

JavaScript框架的强大之处在于简化开发、提升用户体验和应用性能。选择框架时应考虑:1.项目规模和复杂度,2.团队经验,3.生态系统和社区支持。

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

SublimeText3 Linux新版
SublimeText3 Linux最新版

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

禅工作室 13.0.1
功能强大的PHP集成开发环境