翻译连载 | 附录 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,一经查实,立即删除!

相关文章

python 去除不可见字符\x00

读取出来的字节:testa b‘testa\x00’ 去掉\x00:testa.decode().strip(b\x00.decode()) 经测试多个\x00也可以去掉

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

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

RAC(ReactiveCocoa)使用方法(二)

RAC(ReactiveCocoa)使用方法(一)RAC(ReactiveCocoa)使用方法(二) 上篇文章:RAC(ReactiveCocoa)使用方法(一) 中主要介绍了一些RAC中常见类的用法,这篇文章主要总结日常开发中结合一些UI控件的用法。 RAC给常见的很多UI类拓展了用法,使得开发变得越来越简…

unordered_map使用自定义enum作为键值

首先定义模板作为unordered_map的第三个参数&#xff1a; struct EnumClassHash {template <typename T>std::size_t operator()(T t) const{return static_cast<std::size_t>(t);} }; 使用&#xff1a; enum class MyEnum {};std::unordered_map<MyEnum, i…

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

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

数据传址赋值问题

例如&#xff1a; int a 0&#xff1b; void test (&a) { for (int i 0; i < 10; I) { *a; } } 并不能拿到想要的值&#xff0c;反而可能都是随机值&#xff0c;最好使用*a 1。因为*a是先a后*取值&#xff0c;导致指针位置变化&#xff0c;值并没有变。

js 时间格式化

/*** 获取本周、本季度、本月、上月的开始日期、结束日期*/var now new Date(); //当前日期 var nowDayOfWeek now.getDay(); //今天本周的第几天 var nowDay now.getDate(); //当前日 var nowMonth now.getMonth(); //当前月 var nowYear now.getYear(); //当前年 n…

英特尔的六大新技术

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

c++查找pair,使用map,unordered_map,vector

map和unordered_map都可以简单的实现&#xff0c;因为本身就是键值对&#xff0c;而且都提供find方法&#xff0c;相对来说unordered_map比map略快。 vector使用find_if函数&#xff0c;并且最简单的用法就是c 特性中的lambda&#xff0c;例如&#xff1a; static vector<…

判断jQuery选择器结果为空 - CSDN博客

判断jQuery选择器结果为空 - CSDN博客 原文:判断jQuery选择器结果为空 - CSDN博客jQuery选择器获取到的是一个对象&#xff0c;所以无论页面上存在或者不存在元素&#xff0c;这个对象都不为空。因此&#xff0c;如果要使用jQuery检查元素再给某个页面上是否存在的时候&#xf…

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

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

c++中unordered_map的坑

unordered_map本身有hash表&#xff0c;不排序的情况下&#xff0c;对需要键值对的数据处理是比较方便的&#xff0c;存储、查找都很快了&#xff0c;但是如果不注意键值类型的话&#xff0c;有可能会掉坑的。 比如&#xff1a; unordered_map<string, vector<BigTestS…

课后作业-阅读任务-阅读笔记4

1.程序的质量体现在软件外在功能的质量 2.软件开发过程有三个主要的特性&#xff1a;好&#xff0c;快&#xff0c;便宜 3.CMMI有两种不同的实施方法&#xff0c;其级表示不同的内容&#xff1a; &#xff08;1&#xff09;连续式&#xff1a;主要是衡量一个企业在某一项目中的…

c#使用Path.Combine的一个坑

本想先解一个目录&#xff0c;再重组一个新目录存结果文件&#xff0c;想到用Path.Combine可以方便快捷&#xff0c;结果就遇见了坑&#xff0c; windows目录下有驱动器符号如“d:”&#xff0c;这时候使用Path.Combine并不会组成想要的路径&#xff1a; 理想状态下Path.Comb…

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

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

c++ tinyxml2 解析xml小坑

如题&#xff0c;项目中遇到的解析xml小坑&#xff1a; xml是python在windows下生成的&#xff0c;虽然是utf8的&#xff0c;但是回车是16进制显示的0D 0A&#xff0c;与linux下的回车不同&#xff08;0A&#xff09;&#xff0c;导致使用Parse函数时解析失败&#xff0c;查找…

96. Unique Binary Search Trees1和2

/*这道题的关键是&#xff1a;动态表尽量的选取&#xff0c;知道二叉搜索树中左子树的点都比根节点小&#xff0c;右子树的点都比根节点大所以当i为根节点&#xff0c;左子树有i-1个点&#xff0c;右子树有n-i个点&#xff0c;左右子树就可以开始递归构建&#xff0c;过程和一开…

python调用c++的库传递二级指针

遇见的问题&#xff1a;需要从python向c函数中传入二级指针的参数 char**P的问题&#xff1a; c_char_datas (ctypes.c_char_p * file_num)() for j in range(file_num):c_char (ctypes.c_char*6)()c_char.value file_list[j].name.encode(utf-8)c_char_datas[j] ctypes.…

编译tensorflow的小小记录

编译tensorflow有专门的编译工具bazel&#xff0c;所以需要先安装bazel。我使用的是源码编译bazel&#xff08;https://github.com/bazelbuild/bazel/releases&#xff09;。 第一个坑&#xff1a;git clone bazel源码或者使用下载的bazel-0.21.0.tar.gz&#xff0c;这种源码里…

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">内容&…