翻译连载 | 附录 A:Transducing(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
2017-11-23 12:27
483 查看
原文地址: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
我不会把 Transducing 严格的称为“轻量级函数式编程”,它更像是一个顶级的技巧。我把这个技术留到附录来讲意味着你现在很可能并不需要关心它,当你确保你已经非常熟悉整本书的主要内容,你可以再回头看看这一章节。
说实话,即使我已经教过 transducing 很多次了,在写这一章的时候,我仍然需要花很多脑力去理清楚这个技术。所以,如果你看这一章看的很疑惑也没必要感到沮丧。把这一章加个书签,等你觉得你差不多能理解时再回头看看。
Transducing 就是通过减少来转换。
我知道这听起来很令人费解。但是让我们来看看它有多强大。实际上,我认为这是你掌握了轻量级函数式编程后可以做的最好的例证之一。
和这本书的其他部分一样,我的方法是先解释为什么使用这个技术,然后如何使用,最后归结为简单的这个技术到底是什么样的。这通常会有多学很多东西,但是我觉得用这种方式你会更深入的理解它。
在第 3 章中,我们使用这些断言函数来测试一个单词。然后在第 8 章中,我们学习了如何使用像
这个例子可能并不明显,但是这种分开操作相同数组的方式具有一些不理想的地方。当我们处理一个值比较少的数组时一切都还好。但是如果数组中有很多值,每个
当我们的数组是异步/懒惰(也称为 observables)的,随着时间的推移响应事件处理(见第 10 章),会出现类似的性能问题。在这种情况下,一次事件只有一个值,因此使用两个单独的
但是,不太明显的是每个
另一个缺点是可读性,特别是当我们需要对多个数组(或 observable)重复相同的操作时。例如:
显得很重复,对不对?
如果我们可以将
但这不是函数式编程的方式!
在第 8 章中,我们讨论了融合 —— 组合相邻映射函数。回忆一下:
不幸的是,组合相邻断言函数并不像组合相邻映射函数那样容易。为什么呢?想想断言函数长什么“样子” —— 一种描述输入和输出的学术方式。它接收一个单一的参数,返回一个 true 或 false。
如果你试着用
试图组合两个相邻的 reducer 函数同样是行不通的。reducer 函数接收两个值作为输入,并返回单个组合值。reducer 函数的单一返回值也不能作为参数传到另一个需要两个输入的 reducer 函数中。
此外,
考虑像这样的链:
你能想出一个组合能够包含
希望这些例子说明了为什么简单的组合不能胜任这项任务。我们需要一个更强大的技术,而 transducing 就是这个技术。
别太紧张:你不必经历编程过程中所有的探索步骤。一旦你理解了 transducing 能解决的问题,你就可以直接使用函数式编程库中的
让我们开始探索吧。
这是一个不错的改进。我们现在有四个相邻的
在 8 章,我们偷了点懒使用了数组的
在后面我们会来头看看这里是否需要
同样的,我们把
我们的调用链看起来是一样的:
这部分:
让我们为这个通用逻辑定义一个辅助函数。但是我们叫它什么呢?
我们现在用
我们的调用链看起来还是一样的(
c476
这里就不重复写了)。
使用这种形式的辅助函数:
将这些实用函数定义为接收两个参数而不是一个参数不太方便组合,因此我们使用我们的
这看起来有点冗长而且可能不是很有用。
但这实际上是我们进行下一步推导的必要条件。请记住,我们的最终目标是能够
附录 A:Transducing(下)---- 四天后更新
** 【上一章】翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
**
iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。
iKcamp官网:https://www.ikcamp.com
访问官网更快阅读全部免费分享课程:
《iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享》
《iKcamp出品|基于Koa2搭建Node.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
Transducing 是我们这本书要讲到的更为高级的技术。它继承了第 8 章数组操作的许多思想。我不会把 Transducing 严格的称为“轻量级函数式编程”,它更像是一个顶级的技巧。我把这个技术留到附录来讲意味着你现在很可能并不需要关心它,当你确保你已经非常熟悉整本书的主要内容,你可以再回头看看这一章节。
说实话,即使我已经教过 transducing 很多次了,在写这一章的时候,我仍然需要花很多脑力去理清楚这个技术。所以,如果你看这一章看的很疑惑也没必要感到沮丧。把这一章加个书签,等你觉得你差不多能理解时再回头看看。
Transducing 就是通过减少来转换。
我知道这听起来很令人费解。但是让我们来看看它有多强大。实际上,我认为这是你掌握了轻量级函数式编程后可以做的最好的例证之一。
和这本书的其他部分一样,我的方法是先解释为什么使用这个技术,然后如何使用,最后归结为简单的这个技术到底是什么样的。这通常会有多学很多东西,但是我觉得用这种方式你会更深入的理解它。
首先,为什么
让我们从扩展我们在第 3 章中介绍的例子开始,测试单词是否足够短和/或足够长:function isLongEnough(str) { return str.length >= 5; } function isShortEnough(str) { return str.length <= 10; }
在第 3 章中,我们使用这些断言函数来测试一个单词。然后在第 8 章中,我们学习了如何使用像
filter(..)这样的数组操作来重复这些测试。例如:
var words = [ "You", "have", "written", "something", "very", "interesting" ]; words .filter( isLongEnough ) .filter( isShortEnough ); // ["written","something"]
这个例子可能并不明显,但是这种分开操作相同数组的方式具有一些不理想的地方。当我们处理一个值比较少的数组时一切都还好。但是如果数组中有很多值,每个
filter(..)分别处理数组的每个值会比我们预期的慢一点。
当我们的数组是异步/懒惰(也称为 observables)的,随着时间的推移响应事件处理(见第 10 章),会出现类似的性能问题。在这种情况下,一次事件只有一个值,因此使用两个单独的
filter(..)函数处理这些值并不是什么大不了的事情。
但是,不太明显的是每个
filter(..)方法都会产生一个单独的 observable 值。从一个 observable 值中抽出一个值的开销真的可以加起来(译者注:详情请看第 10 章的“积极的 vs 惰性的”这一节)。这是真实存在的,因为在这些情况下,处理数千或数百万的值并不罕见; 所以,即使是这么小的成本也会很快累加起来。
另一个缺点是可读性,特别是当我们需要对多个数组(或 observable)重复相同的操作时。例如:
zip( list1.filter( isLongEnough ).filter( isShortEnough ), list2.filter( isLongEnough ).filter( isShortEnough ), list3.filter( isLongEnough ).filter( isShortEnough ) )
显得很重复,对不对?
如果我们可以将
isLongEnough(..)断言与
isShortEnough(..)断言组合在一起是不是会更好一点呢(可读性和性能)?你可以手动执行:
function isCorrectLength(str) { return isLongEnough( str ) && isShortEnough( str ); }
但这不是函数式编程的方式!
在第 8 章中,我们讨论了融合 —— 组合相邻映射函数。回忆一下:
words .map( pipe( removeInvalidChars, upper, elide ) );
不幸的是,组合相邻断言函数并不像组合相邻映射函数那样容易。为什么呢?想想断言函数长什么“样子” —— 一种描述输入和输出的学术方式。它接收一个单一的参数,返回一个 true 或 false。
如果你试着用
isshortenough(islongenough(str)),这是行不通的。因为
islongenough(..)会返回 true 或者 false ,而不是返回
isshortenough(..)所要的字符串类型的值。这可真倒霉。
试图组合两个相邻的 reducer 函数同样是行不通的。reducer 函数接收两个值作为输入,并返回单个组合值。reducer 函数的单一返回值也不能作为参数传到另一个需要两个输入的 reducer 函数中。
此外,
reduce(..)辅助函数可以接收一个可选的
initialValue输入。有时可以省略,但有时候它又必须被传入。这就让组合更复杂了,因为一个
reduce(..)可能需要一个
initialValue,而另一个
reduce(..)可能需要另一个
initialValue。所以我们怎么可能只用某种组合的 reducer 来实现
reduce(..)呢。
考虑像这样的链:
words .map( strUppercase ) .filter( isLongEnough ) .filter( isShortEnough ) .reduce( strConcat, "" ); // "WRITTENSOMETHING"
你能想出一个组合能够包含
map(strUppercase),
filter(isLongEnough),
filter(isShortEnough),
reduce(strConcat)所有这些操作吗?每种操作的行为是不同的,所以不能直接组合在一起。我们需要把它们修改下让它们组合在一起。
希望这些例子说明了为什么简单的组合不能胜任这项任务。我们需要一个更强大的技术,而 transducing 就是这个技术。
如何,下一步
让我们谈谈我们该如何得到一个能组合映射,断言和/或 reducers 的框架。别太紧张:你不必经历编程过程中所有的探索步骤。一旦你理解了 transducing 能解决的问题,你就可以直接使用函数式编程库中的
transduce(..)工具继续你应用程序的剩余部分!
让我们开始探索吧。
把 Map/Filter 表示为 Reduce
我们要做的第一件事情就是将我们的filter(..)和
map(..)调用变为
reduce(..)调用。回想一下我们在第 8 章是怎么做的:
function strUppercase(str) { return str.toUpperCase(); } function strConcat(str1,str2) { return str1 + str2; } function strUppercaseReducer(list,str) { list.push( strUppercase( str ) ); return list; } function isLongEnoughReducer(list,str) { if (isLongEnough( str )) list.push( str ); return list; } function isShortEnoughReducer(list,str) { if (isShortEnough( str )) list.push( str ); return list; } words .reduce( strUppercaseReducer, [] ) .reduce( isLongEnoughReducer, [] ) .reduce( isShortEnough, [] ) .reduce( strConcat, "" ); // "WRITTENSOMETHING"
这是一个不错的改进。我们现在有四个相邻的
reduce(..)调用,而不是三种不同方法的混合。然而,我们仍然不能
compose(..)这四个 reducer,因为它们接受两个参数而不是一个参数。
在 8 章,我们偷了点懒使用了数组的
push方法而不是
concat(..)方法返回一个新数组,导致有副作用。现在让我们更正式一点:
function strUppercaseReducer(list,str) { return list.concat( [strUppercase( str )] ); } function isLongEnoughReducer(list,str) { if (isLongEnough( str )) return list.concat( [str] ); return list; } function isShortEnoughReducer(list,str) { if (isShortEnough( str )) return list.concat( [str] ); return list; }
在后面我们会来头看看这里是否需要
concat(..)。
参数化 Reducers
除了使用不同的断言函数之外,两个 filter reducers 几乎相同。让我们把这些 reducers 参数化得到一个可以定义任何 filter-reducer 的工具函数:function filterReducer(predicateFn) { return function reducer(list,val){ if (predicateFn( val )) return list.concat( [val] ); return list; }; } var isLongEnoughReducer = filterReducer( isLongEnough ); var isShortEnoughReducer = filterReducer( isShortEnough );
同样的,我们把
mapperFn(..)也参数化来生成 map-reducer 函数:
function mapReducer(mapperFn) { return function reducer(list,val){ return list.concat( [mapperFn( val )] ); }; } var strToUppercaseReducer = mapReducer( strUppercase );
我们的调用链看起来是一样的:
words .reduce( strUppercaseReducer, [] ) .reduce( isLongEnoughReducer, [] ) .reduce( isShortEnough, [] ) .reduce( strConcat, "" );
提取共用组合逻辑
仔细观察上面的mapReducer(..)和
filterReducer(..)函数。你发现共享功能了吗?
这部分:
return list.concat( .. ); // 或者 return list;
让我们为这个通用逻辑定义一个辅助函数。但是我们叫它什么呢?
function WHATSITCALLED(list,val) { return list.concat( [val] ); }
WHATSITCALLED(..)函数做了些什么呢,它接收两个参数(一个数组和另一个值),将值 concat 到数组的末尾返回一个新的数组。所以这个
WHATSITCALLED(..)名字不合适,我们可以叫它
listCombination(..):
function listCombination(list,val) { return list.concat( [val] ); }
我们现在用
listCombination(..)来重新定义我们的 reducer 辅助函数:
function mapReducer(mapperFn) { return function reducer(list,val){ return listCombination( list, mapperFn( val ) ); }; } function filterReducer(predicateFn) { return function reducer(list,val){ if (predicateFn( val )) return listCombination( list, val ); return list; }; }
我们的调用链看起来还是一样的(
c476
这里就不重复写了)。
参数化组合
我们的listCombination(..)小工具只是组合两个值的一种方式。让我们将它的用途参数化,以使我们的 reducers 更加通用:
function mapReducer(mapperFn,combinationFn) { return function reducer(list,val){ return combinationFn( list, mapperFn( val ) ); }; } function filterReducer(predicateFn,combinationFn) { return function reducer(list,val){ if (predicateFn( val )) return combinationFn( list, val ); return list; }; }
使用这种形式的辅助函数:
var strToUppercaseReducer = mapReducer( strUppercase, listCombination ); var isLongEnoughReducer = filterReducer( isLongEnough, listCombination ); var isShortEnoughReducer = filterReducer( isShortEnough, listCombination );
将这些实用函数定义为接收两个参数而不是一个参数不太方便组合,因此我们使用我们的
curry(..)(柯里化)方法:
var curriedMapReducer = curry( function mapReducer(mapperFn,combinationFn){ return function reducer(list,val){ return combinationFn( list, mapperFn( val ) ); }; } ); var curriedFilterReducer = curry( function filterReducer(predicateFn,combinationFn){ return function reducer(list,val){ if (predicateFn( val )) return combinationFn( list, val ); return list; }; } ); var strToUppercaseReducer = curriedMapReducer( strUppercase )( listCombination ); var isLongEnoughReducer = curriedFilterReducer( isLongEnough )( listCombination ); var isShortEnoughReducer = curriedFilterReducer( isShortEnough )( listCombination );
这看起来有点冗长而且可能不是很有用。
但这实际上是我们进行下一步推导的必要条件。请记住,我们的最终目标是能够
compose(..)这些 reducers。我们快要完成了。
附录 A:Transducing(下)---- 四天后更新
** 【上一章】翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
**
iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。
iKcamp官网:https://www.ikcamp.com
访问官网更快阅读全部免费分享课程:
《iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享》
《iKcamp出品|基于Koa2搭建Node.js实战项目教程》
包含:文章、视频、源代码
相关文章推荐
- 翻译连载 | 附录 A:Transducing(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 附录 C:函数式编程函数库-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 附录 A:Transducing(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 附录 B: 谦虚的 Monad-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 附录 B: 谦虚的 Monad-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | JavaScript轻量级函数式编程-第4章:组合函数 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 7 章: 闭包 vs 对象
- 翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | JavaScript 轻量级函数式编程-第2章:函数基础 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 6 章:值的不可变性
- 翻译连载 |《你不知道的JS》姊妹篇 | JavaScript 轻量级函数式编程-引言&前言
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 5 章:减少副作用
- 翻译连载 | 第 10 章:异步的函数式(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 4 章:组合函数
- 翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 3 章:管理函数的输入
- 翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 2 章:函数基础