翻译连载 | 附录 A:Transducing(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇...

  • 原文地址:Functional-Light-JS
  • 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者

关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTML 最坚实的梁柱;分享,是 CSS 里最闪耀的一瞥;总结,是 JavaScript 中最严谨的逻辑。经过捶打磨练,成就了本书的中文版。本书包含了函数式编程之精髓,希望可以帮助大家在学习函数式编程的道路上走的更顺畅。比心。

译者团队(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、萝卜、vavd317、vivaxy、萌萌、zhouyao

JavaScript 轻量级函数式编程

附录 A:Transducing(下)

组合柯里化

这一步是最棘手的。所以请慢慢的用心的阅读。

让我们看看没有将 listCombination(..) 传递给柯里化函数的样子:

var x = curriedMapReducer( strUppercase );
var y = curriedFilterReducer( isLongEnough );
var z = curriedFilterReducer( isShortEnough );

看看这三个中间函数 x(..)y(..)z(..)。每个函数都期望得到一个单一的组合函数并产生一个 reducer 函数。

记住,如果我们想要所有这些的独立的 reducer,我们可以这样做:

var upperReducer = x( listCombination );
var longEnoughReducer = y( listCombination );
var shortEnoughReducer = z( listCombination );

但是,如果你调用 y(z),会得到什么呢?当把 z 传递给 y(..) 调用,而不是 combinationFn(..) 时会发生什么呢?这个返回的 reducer 函数内部看起来像这样:

function reducer(list,val) {if (isLongEnough( val )) return z( list, val );return list;
}

看到 z(..) 里面的调用了吗? 这看起来应该是错误的,因为 z(..) 函数应该只接收一个参数(combinationFn(..)),而不是两个参数(list 和 val)。这和要求不匹配。不行。

我们来看看组合 y(z(listCombination))。我们将把它分成两个不同的步骤:

var shortEnoughReducer = z( listCombination );
var longAndShortEnoughReducer = y( shortEnoughReducer );

我们创建 shortEnoughReducer(..),然后将它作为 combinationFn(..) 传递给 y(..),生成 longAndShortEnoughReducer(..)。多读几遍,直到理解。

现在想想: shortEnoughReducer(..)longAndShortEnoughReducer(..) 的内部构造是什么样的呢?你能想得到吗?

// shortEnoughReducer, from z(..):
function reducer(list,val) {if (isShortEnough( val )) return listCombination( list, val );return list;
}// longAndShortEnoughReducer, from y(..):
function reducer(list,val) {if (isLongEnough( val )) return shortEnoughReducer( list, val );return list;
}

你看到 shortEnoughReducer(..) 替代了 longAndShortEnoughReducer(..) 里面 listCombination(..) 的位置了吗? 为什么这样也能运行?

因为 reducer(..) 的“形状”和 listCombination(..) 的形状是一样的。 换句话说,reducer 可以用作另一个 reducer 的组合函数; 它们就是这样组合起来的! listCombination(..) 函数作为第一个 reducer 的组合函数,这个 reducer 又可以作为组合函数给下一个 reducer,以此类推。

我们用几个不同的值来测试我们的 longAndShortEnoughReducer(..)

longAndShortEnoughReducer( [], "nope" );
// []longAndShortEnoughReducer( [], "hello" );
// ["hello"]longAndShortEnoughReducer( [], "hello world" );
// []

longAndShortEnoughReducer(..) 会过滤出不够长且不够短的值,它在同一步骤中执行这两个过滤。这是一个组合 reducer!

再花点时间消化下。

现在,把 x(..) (生成大写 reducer 的产生器)加入组合:

var longAndShortEnoughReducer = y( z( listCombination) );
var upperLongAndShortEnoughReducer = x( longAndShortEnoughReducer );

正如 upperLongAndShortEnoughReducer(..) 名字所示,它同时执行所有三个步骤 - 一个映射和两个过滤器!它内部看起来是这样的:

// upperLongAndShortEnoughReducer:
function reducer(list,val) {return longAndShortEnoughReducer( list, strUppercase( val ) );
}

一个字符串类型的 val 被传入,由 strUppercase(..) 转换成大写,然后传递给 longAndShortEnoughReducer(..)。该函数只有在 val 满足足够长且足够短的条件时才将它添加到数组中。否则数组保持不变。

我花了几个星期来思考分析这种杂耍似的操作。所以别着急,如果你需要在这好好研究下,重新阅读个几(十几个)次。慢慢来。

现在来验证一下:

upperLongAndShortEnoughReducer( [], "nope" );
// []upperLongAndShortEnoughReducer( [], "hello" );
// ["HELLO"]upperLongAndShortEnoughReducer( [], "hello world" );
// []

这个 reducer 成功的组合了和 map 和两个 filter,太棒了!

让我们回顾一下我们到目前为止所做的事情:

var x = curriedMapReducer( strUppercase );
var y = curriedFilterReducer( isLongEnough );
var z = curriedFilterReducer( isShortEnough );var upperLongAndShortEnoughReducer = x( y( z( listCombination ) ) );words.reduce( upperLongAndShortEnoughReducer, [] );
// ["WRITTEN","SOMETHING"]

这已经很酷了,但是我们可以让它更好。

x(y(z( .. ))) 是一个组合。我们可以直接跳过中间的 x / y / z 变量名,直接这么表示该组合:

var composition = compose(curriedMapReducer( strUppercase ),curriedFilterReducer( isLongEnough ),curriedFilterReducer( isShortEnough )
);var upperLongAndShortEnoughReducer = composition( listCombination );words.reduce( upperLongAndShortEnoughReducer, [] );
// ["WRITTEN","SOMETHING"]

我们来考虑下该组合函数中“数据”的流动:

  1. listCombination(..) 作为组合函数传入,构造 isShortEnough(..) 过滤器的 reducer。

  2. 然后,所得到的 reducer 函数作为组合函数传入,继续构造 isShortEnough(..) 过滤器的 reducer。

  3. 最后,所得到的 reducer 函数作为组合函数传入,构造 strUppercase(..) 映射的 reducer。

在前面的片段中,composition(..) 是一个组合函数,期望组合函数来形成一个 reducer;而这个 composition(..) 有一个特殊的标签:transducer。给 transducer 提供组合函数产生组合的 reducer:

// TODO:检查 transducer 是产生 reducer 还是它本身就是 reducer

var transducer = compose(curriedMapReducer( strUppercase ),curriedFilterReducer( isLongEnough ),curriedFilterReducer( isShortEnough )
);words
.reduce( transducer( listCombination ), [] );
// ["WRITTEN","SOMETHING"]

注意:我们应该好好观察下前面两个片段中的 compose(..) 顺序,这地方有点难理解。回想一下,在我们的原始示例中,我们先 map(strUppercase) 然后 filter(isLongEnough) ,最后 filter(isShortEnough);这些操作实际上也确实按照这个顺序执行的。但在第 4 章中,我们了解到,compose(..) 通常是以相反的顺序运行。那么为什么我们不需要反转这里的顺序来获得同样的期望结果呢?来自每个 reducer 的 combinationFn(..) 的抽象反转了操作顺序。所以和直觉相反,当组合一个 tranducer 时,你只需要按照实际的顺序组合就好!

列表组合:纯与不纯

我们再来看一下我们的 listCombination(..) 组合函数的实现:

function listCombination(list,val) {return list.concat( [val] );
}

虽然这种方法是纯的,但它对性能有负面影响。首先,它创建临时数组来包裹 val。然后,concat(..) 方法创建一个全新的数组来连接这个临时数组。每一步都会创建和销毁的很多数组,这不仅对 CPU 不利,也会造成 GC 内存的流失。

下面是性能更好但是不纯的版本:

function listCombination(list,val) {list.push( val );return list;
}

单独的考虑下 listCombination(..) ,毫无疑问,这是不纯的,这通常是我们想要避免的。但是,我们应该考虑一个更大的背景。

listCombination(..) 不是我们完全有交互的函数。我们不直接在程序中的任何地方使用它,而只是在 transducing 的过程中使用它。

回到第 5 章,我们定义纯函数来减少副作用的目标只是限制在应用的 API 层级。对于底层实现,只要没有违反对外部是纯函数,就可以在函数内为了性能而变得不纯。

listCombination(..) 更多的是转换的内部实现细节。实际上,它通常由 transducing 库提供!而不是你的程序中进行交互的顶层方法。

底线:我认为甚至使用 listCombination(..) 的性能最优但是不纯的版本也是完全可以接受的。只要确保你用代码注释记录下它不纯即可!

可选的组合

到目前为止,这是我们用转换所得到的:

words
.reduce( transducer( listCombination ), [] )
.reduce( strConcat, "" );
// 写点什么

这已经非常棒了,但是我们还藏着最后一个的技巧。坦白来说,我认为这部分能够让你迄今为止付出的所有努力变得值得。

我们可以用某种方式实现只用一个 reduce(..) 来“组合”这两个 reduce(..) 吗? 不幸的是,我们并不能将 strConcat(..) 添加到 compose(..) 调用中; 它的“形状”不适用于那个组合。

但是让我们来看下这两个功能:

function strConcat(str1,str2) { return str1 + str2; }function listCombination(list,val) { list.push( val ); return list; }

如果你用心观察,可以看出这两个功能是如何互换的。它们以不同的数据类型运行,但在概念上它们也是一样的:将两个值组合成一个。

换句话说, strConcat(..) 是一个组合函数!

这意味着如果我们的最终目标是获得字符串连接而不是数组,我们就可以用它代替 listCombination(..)

words.reduce( transducer( strConcat ), "" );
// 写点什么

Boom! 这就是 transducing。

最后

深吸一口气,确实有很多要消化。

放空我们的大脑,让我们把注意力转移到如何在我们的程序中使用转换,而不是关心它的工作原理。

回想起我们之前定义的辅助函数,为清楚起见,我们重新命名一下:

var transduceMap = curry( function mapReducer(mapperFn,combinationFn){return function reducer(list,v){return combinationFn( list, mapperFn( v ) );};
} );var transduceFilter = curry( function filterReducer(predicateFn,combinationFn){return function reducer(list,v){if (predicateFn( v )) return combinationFn( list, v );return list;};
} );

还记得我们这样使用它们:

var transducer = compose(transduceMap( strUppercase ),transduceFilter( isLongEnough ),transduceFilter( isShortEnough )
);

transducer(..) 仍然需要一个组合函数(如 listCombination(..)strConcat(..))来产生一个传递给 reduce(..) (连同初始值)的 transduce-reducer 函数。

但是为了更好的表达所有这些转换步骤,我们来做一个 transduce(..) 工具来为我们做这些步骤:

function transduce(transducer,combinationFn,initialValue,list) {var reducer = transducer( combinationFn );return list.reduce( reducer, initialValue );
}

这是我们的运行示例,梳理如下:

var transducer = compose(transduceMap( strUppercase ),transduceFilter( isLongEnough ),transduceFilter( isShortEnough )
);transduce( transducer, listCombination, [], words );
// ["WRITTEN","SOMETHING"]transduce( transducer, strConcat, "", words );
// 写点什么

不错,嗯! 看到 listCombination(..)strConcat(..) 函数可以互换使用组合函数了吗?

Transducers.js

最后,我们来说明我们运行的例子,使用sensors-js库(https://github.com/cognitect-labs/transducers-js ):

var transformer = transducers.comp(transducers.map( strUppercase ),transducers.filter( isLongEnough ),transducers.filter( isShortEnough )
);transducers.transduce( transformer, listCombination, [], words );
// ["WRITTEN","SOMETHING"]transducers.transduce( transformer, strConcat, "", words );
// WRITTENSOMETHING

看起来几乎与上述相同。

注意: 上面的代码段使用 transformers.comp(..) ,因为这个库提供这个 API,但在这种情况下,我们从第 4 章的 compose(..) 也将产生相同的结果。换句话说,组合本身不是 transducing 敏感的操作。

该片段中的组合函数被称为 transformer ,而不是 transducer。那是因为如果我们直接调用 transformer(listCombination)(或 transformer(strConcat)),那么我们不会像以前那样得到一个直观的 transduce-reducer 函数。

transducers.map(..)transducers.filter(..) 是特殊的辅助函数,可以将常规的断言函数或映射函数转换成适用于产生特殊变换对象的函数(里面包含了 reducer 函数);这个库使用这些变换对象进行转换。此转换对象抽象的额外功能超出了我们将要探索的内容,请参阅该库的文档以获取更多信息。

由于 transformer(..) 产生一个变换对象,而不是一个典型的二元 transduce-reducer 函数,该库还提供 toFn(..) 来使变换对象适应本地数组的 reduce(..) 方法:

words.reduce(transducers.toFn( transformer, strConcat ),""
);
// WRITTENSOMETHING

into(..) 是另一个提供的辅助函数,它根据指定的空/初始值的类型自动选择默认的组合函数:

transducers.into( [], transformer, words );
// ["WRITTEN","SOMETHING"]transducers.into( "", transformer, words );
// WRITTENSOMETHING

当指定一个空数组 [] 时,内部的 transduce(..) 使用一个默认的函数实现,这个函数就像我们的 listCombination(..)。但是当指定一个空字符串 “” 时,会使用像我们的 strConcat(..) 这样的方法。这很酷!

如你所见,transducers-js 库使转换非常简单。我们可以非常有效地利用这种技术的力量,而不至于陷入定义所有这些中间转换器生产工具的繁琐过程中去。

总结

Transduce 就是通过减少来转换。更具体点,transduer 是可组合的 reducer。

我们使用转换来组合相邻的map(..)filter(..)reduce(..) 操作。我们首先将 map(..)filter(..) 表示为 reduce(..),然后抽象出常用的组合操作来创建一个容易组合的一致的 reducer 生成函数。

transducing 主要提高性能,如果在延迟序列(异步 observables)中使用,则这一点尤为明显。

但是更广泛地说,transducing 是我们针对那些不能被直接组合的函数,使用的一种更具声明式风格的方法。否则这些函数将不能直接组合。如果使用这个技术能像使用本书中的所有其他技术一样用的恰到好处,代码就会显得更清晰,更易读! 使用 transducer 进行单次 reduce(..) 调用比追踪多个 reduce(..) 调用更容易理解。

** 【上一章】翻译连载 | 附录 A:Transducing(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇 **

076d086e1d56bd0fb1f51c2abd0b8c08

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

iKcamp官网:https://www.ikcamp.com
访问官网更快阅读全部免费分享课程:
《iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享》
《iKcamp出品|基于Koa2搭建Node.js实战项目教程》
包含:文章、视频、源代码

转载于:https://www.cnblogs.com/ikcamp/p/7918852.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/487205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

思未尽,行致远丨记 IBM 成立 109周年

来源:IBM中国主创团队(IBM Social & Studio DL team)策划:点亮 IBM 庆生蜡烛的甜心御姐 June撰稿:被百年“名场面”感动 cry 的原创一哥 Raphal设计:熬出黑眼圈也要操刀设计的鲁先森排版:话…

王恩哥院士:信息化发展进程中,科学、基础研究和技术、应用都是关键

图为王恩哥院士接受新华网采访来源:新华网由深圳市人民政府指导,中国信息化百人会主办,华为技术有限公司协办的中国信息化百人会2020年峰会于8月7日启幕。会上,中国信息化百人会顾问、中国科学院院士、北京大学原校长、中国科学院…

英特尔的六大新技术

来源:芯东西(公众号:aichip001) 芯东西8月14日消息,昨日晚间,英特尔在2020年架构日上推出10nm SuperFin晶体管技术,将实现其有史以来最强大的单节点内性能增强。据悉,10nm SuperFin技…

中国芯片设计云技术白皮书2.0发布

来源:摩尔精英作为行业内专业的IT/CAD技术服务团队,摩尔精英IT/CAD事业部曾于2019年11月21日的南京ICCAD大会上发表的《芯片设计云计算白皮书1.0》中,初步探索了基于公有云的EDA计算平台的实现方案。随着进一步的探索和方案优化,我…

希尔伯特著名的第六问题 – 原来麦克斯韦早就有解?

转自:知社学术圈作者:Natalie Wolchover 编译:子聿希尔伯特的第六大问题号召人们公理化物理学定律,也就是说从一套初始假设或者公理的基础出发严格构建它们。这样做将会揭示需要不同公设的定律之间的矛盾。从相同的公理出发推导…

layui富文本编译器添加图片

1、创建富文本编辑器 <form class"layui-form" method"post" id"myForm" enctype"multipart/form-data"><div class"layui-form-item layui-form-text"><label class"layui-form-label">内容&…

为什么数学是理解世界的最佳方式

来源&#xff1a;遇见数学在对着乔治梅森大学最近的一届新生致辞时&#xff0c;丽贝卡戈尔丁&#xff08;Rebecca Goldin&#xff09;传递了一个令人沮丧的数据&#xff1a;最近的一项研究显示&#xff0c;36%的大学生在大学四年时间里批判性思维并未显著提高。戈尔丁解释说&am…

5G 标准的走向,为何越来越不同寻常?

作者 | Michael Koziol译者 | 弯月&#xff0c;责编 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;以下为译文&#xff1a;如果没有互联网工程任务组&#xff08;Internet Engineering Task Force&#xff09;制定种种互联网标准&#xff0c;那么此时此刻你…

WebSocket教程(一)

一、websocket与http WebSocket是HTML5出的东西&#xff08;协议&#xff09;&#xff0c;也就是说HTTP协议没有变化&#xff0c;或者说没关系&#xff0c;但HTTP是不支持持久连接的&#xff08;长连接&#xff0c;循环连接的不算&#xff09; 首先HTTP有 1.1 和 1.0 之说&…

人类智慧的本质是什么?【知社视频】第118期

来源&#xff1a;知社学术圈自古希腊开始&#xff0c;人们就认为智慧是人的根本属性&#xff0c;哲学家普罗泰戈拉有句名言&#xff0c;“人是万物的尺度&#xff0c;是存在的事物存在的尺度&#xff0c;也是不存在的事物不存在的尺度”。马克思主义也认为&#xff0c;认识世界…

20个!中国科协发布2020年重大科学问题和工程技术难题

来源&#xff1a; 中国科学报作者 | 高雅丽8月15日&#xff0c;中国科协在第二十二届中国科协年会闭幕式上发布了 10个对科学发展具有导向作用的科学问题和10个对技术和产业具有关键作用的工程难题。10个前沿科学问题为&#xff1a;冠状病毒跨种传播的生态学机制是什么&#xf…

图神经网络的表达能力,究竟有多强大?

来源&#xff1a;AI科技评论作者 | Mr Bear编辑 | 丛 末近年来&#xff0c;随着图神经网络在各个领域的火热应用&#xff0c;越来越多的学者试图从图论的角度对图神经网络的表达能力进行理论分析&#xff0c;并基于这些理论分析开发出了性能强大的模型。然而&#xff0c;在实际…

在Mac(OS X)中使用GitHub的超详细攻略(20170706)

转自&#xff1a;http://blog.csdn.net/baimafujinji/article/details/74533992 GitHub是一个面向开源及私有软件项目的托管平台、开源代码库以及版本控制系统&#xff0c;因为只支持 Git 作为唯一的版本库格式进行托管&#xff0c;故名 GitHub。通常在Windows下使用GitHub的教…

低时延AI完美适配工业场景,边缘智能如何构建数字工业新生态?

物联网智库 原创二次转载请联系原作者今年年初爆发的新冠疫情迫使居民的生活、学习、工作由线下向线上大规模迁移&#xff0c;令各行各业意识到了数字化升级的重要性&#xff0c;纷纷踏上转型之路。联网设备数量也随着物联网技术的快速发展而不断飙升&#xff0c;越来越多的应用…

AndroidStudio 3.4更新了啥?(转载)

版权声明&#xff1a;本文为原博主收集的资料&#xff0c;欢迎参考。未经本人允许&#xff0c;禁止转载。 原博文地址&#xff1a;https://blog.csdn.net/z302766296/article/details/89468726 每次到AndroidStudio新版本发布的时候&#xff0c;都忍不住想更新一波&#xff0c;…

图片缓存

转载于:https://www.cnblogs.com/cyruszhu/p/7995117.html

Nvidia真的收购Arm了吗?

来源&#xff1a;半导体行业观察综合自网络&#xff0c;谢谢。 近日有消息显示&#xff0c;Nvidia已成功达成收购Arm。但笔者通过翻阅外媒报道的原文中看&#xff0c;其实并非如此。据英国媒体EveningStandard报道&#xff0c;英伟达&#xff08;NVIDIA&#xff09;收购Arm已进…

刘江川院士:边缘计算如何应对能源互联网的碎片化和复杂性? | CCF-GAIR 2020

来源&#xff1a;雷锋网作者 | 王刚雷锋网按&#xff1a;2020 年8月7日&#xff0c;全球人工智能和机器人峰会&#xff08;CCF-GAIR 2020&#xff09;正式开幕。CCF-GAIR 2020 峰会由中国计算机学会&#xff08;CCF&#xff09;主办&#xff0c;雷锋网、香港中文大学&#xff0…

调查:人工智能技术的应用现状

本文最初发表在 Towards Data Science 博客上&#xff0c;经原作者 Luke Posey 授权&#xff0c;InfoQ 中文站翻译并分享。作者 | Luke Posey译者 | Sambodhi策划 & 编辑 | 刘燕随着工具和基础设施的成熟&#xff0c;应用人工智能不断加速发展。将这些基础设施与强大的人才…

LoRa VS NB-IoT,一场物联网时代C位争夺战

来源&#xff1a;脑极体我国5G商用已经一年多了&#xff0c;比起5G网络所带来的极致体验&#xff0c;我们对于西方世界因对5G网络的安全担忧所引发的一系列事件恐怕更加深有感触。美国跳脚、英国退网&#xff0c;中国在5G技术的领先深深刺痛了这些不可一世的西方大国。而原本可…