javascript函数式编程(代码片段)

wangking wangking     2022-12-10     778

关键词:

JavaScript函数式编程(一) 

JavaScript函数式编程(二)

在第二篇文章里,我们介绍了 Maybe、Either、IO 等几种常见的 Functor,或许很多看完第二篇文章的人都会有疑惑:

『这些东西有什么卵用?』

事实上,如果只是为了学习编写函数式、副作用小的代码的话,看完第一篇文章就足够了。第二篇文章和这里的第三篇着重于的是一些函数式理论的实践,是的,这些很难(但并非不可能)应用到实际的生产中,因为很多轮子都已经造好了并且很好用了。比如现在在前端大规模使用的 Promise 这种异步调用规范,其实就是一种 Monad(等下会讲到);现在日趋成熟的 Redux 作为一种 FLUX 的变种实现,核心理念也是状态机和函数式编程。

一、Monad

关于 Monad 的介绍和教程在网络上已经层出不穷了比如这篇文章

函数编程中functor和monad的形象解释

,很多文章都写得比我下面的更好,所以我在这里只是用一种更简单易懂的方式介绍 Monad,当然简单易懂带来的坏处就是不严谨,所以见谅/w\\

如果你对 Promise 这种规范有了解的话,应该记得 Promise 里一个很惊艳的特性:

 1  
 2 doSomething()
 3  
 4 .then(result => 
 5  
 6 // 你可以return一个Promise链!
 7  
 8 return fetch(‘url‘).then(result => parseBody(result));
 9  
10 )
11  
12 .then(result => 
13  
14 // 这里的result是上面那个Promise的终值
15  
16 )
17  
18  
19  
20 doSomething()
21  
22 .then(result => 
23  
24 // 也可以直接return一个具体的值!
25  
26 return 123;
27  
28 )
29  
30 .then(result => 
31  
32 // result === 123
33  
34 )

 

对于 Promise 的一个回调函数来说,它既可以直接返回一个值,也可以返回一个新的 Promise,但对于他们后续的回调函数来说,这二者都是等价的,这就很巧妙地解决了 nodejs 里被诟病已久的嵌套地狱。

事实上,Promise 就是一种 Monad,是的,可能你天天要写一大堆 Promise,可直到现在才知道天天用的这个东西竟然是个听起来很高大上的函数式概念。

下面我们来实际实现一个 Monad,如果你不想看的话,只要记住 『Promise 就是一种 Monad』 这句话然后直接跳过这一章就好了。

我们来写一个函数 cat,这个函数的作用和 Linux 命令行下的 cat 一样,读取一个文件,然后打出这个文件的内容,这里 IO 的实现请参考上一篇文章:

 1  
 2 import fs from ‘fs‘;
 3  
 4 import _ from ‘lodash‘;
 5  
 6  
 7  
 8 var map = _.curry((f, x) => x.map(f));
 9  
10 var compose = _.flowRight;
11  
12  
13  
14 var readFile = function(filename) 
15  
16 return new IO(_ => fs.readFileSync(filename, ‘utf-8‘));
17  
18 ;
19  
20  
21  
22 var print = function(x) 
23  
24 return new IO(_ => 
25  
26 console.log(x);
27  
28 return x;
29  
30 );
31  
32 
33  
34  
35  
36 var cat = compose(map(print), readFile);
37  
38  
39  
40 cat("file")
41  
42 //=> IO(IO("file的内容"))

 

由于这里涉及到两个 IO:读取文件和打印,所以最后结果就是我们得到了两层 IO,想要运行它,只能调用:

 
cat("file").__value().__value();
 
//=> 读取文件并打印到控制台

 

很尴尬对吧,如果我们涉及到 100 个 IO 操作,那么难道要连续写 100 个 __value() 吗?

当然不能这样不优雅,我们来实现一个 join 方法,它的作用就是剥开一层 Functor,把里面的东西暴露给我们:

 1  
 2 var join = x => x.join();
 3  
 4 IO.prototype.join = function() 
 5  
 6 return this.__value ? IO.of(null) : this.__value();
 7  
 8 
 9  
10  
11  
12 // 试试看
13  
14 var foo = IO.of(IO.of(‘123‘));
15  
16  
17  
18 foo.join();
19  
20 //=> IO(‘123‘)

 

有了 join 方法之后,就稍微优雅那么一点儿了:

var cat = compose(join, map(print), readFile);
 
cat("file").__value();
 
//=> 读取文件并打印到控制台

 

join 方法可以把 Functor 拍平(flatten),我们一般把具有这种能力的 Functor 称之为 Monad。

这里只是非常简单地移除了一层 Functor 的包装,但作为优雅的程序员,我们不可能总是在 map 之后手动调用 join 来剥离多余的包装,否则代码会长得像这样:

var doSomething = compose(join, map(f), join, map(g), join, map(h));

 

所以我们需要一个叫 chain 的方法来实现我们期望的链式调用,它会在调用 map 之后自动调用 join 来去除多余的包装,这也是 Monad 的一大特性:

 1  
 2 var chain = _.curry((f, functor) => functor.chain(f));
 3  
 4 IO.prototype.chain = function(f) 
 5  
 6 return this.map(f).join();
 7  
 8 
 9  
10  
11  
12 // 现在可以这样调用了
13  
14 var doSomething = compose(chain(f), chain(g), chain(h));
15  
16  
17  
18 // 当然,也可以这样
19  
20 someMonad.chain(f).chain(g).chain(h)
21  
22  
23  
24 // 写成这样是不是很熟悉呢?
25  
26 readFile(‘file‘)
27  
28 .chain(x => new IO(_ => 
29  
30 console.log(x);
31  
32 return x;
33  
34 ))
35  
36 .chain(x => new IO(_ => 
37  
38 // 对x做一些事情,然后返回
39  
40 ))

 

哈哈,你可能看出来了,chain 不就类似 Promise 中的 then 吗?是的,它们行为上确实是一致的(then 会稍微多一些逻辑,它会记录嵌套的层数以及区别 Promise 和普通返回值),Promise 也确实是一种函数式的思想。

(我本来想在下面用 Promise 为例写一些例子,但估计能看到这里的人应该都能熟练地写各种 Promise 链了,所以就不写了0w0)

总之就是,Monad 让我们避开了嵌套地狱,可以轻松地进行深度嵌套的函数式编程,比如IO和其它异步任务。

 

二、函数式编程的应用

好了,关于函数式编程的一些基础理论的介绍就到此为止了,如果想了解更多的话其实建议去学习 Haskell 或者 Lisp 这样比较正统的函数式语言。下面我们来回答一个问题:函数式编程在实际应用中到底有啥用咧?

1、React

React 现在已经随处可见了,要问它为什么流行,可能有人会说它『性能好』、『酷炫』、『第三方组件丰富』、『新颖』等等,但这些都不是最关键的,最关键是 React 给前端开发带来了全新的理念:函数式和状态机。

我们来看看 React 怎么写一个『纯组件』吧:

 
var Text = props => (
 
<div style=props.style>props.text</div>
 
)

 

咦这不就是纯函数吗?对于任意的 text 输入,都会产生唯一的固定输出,只不过这个输出是一个 virtual DOM 的元素罢了。配合状态机,就大大简化了前端开发的复杂度:

state => virtual DOM => 真实 DOM

 

在 Redux 中更是可以把核心逻辑抽象成一个纯函数 reducer:

reducer(currentState, action) => newState

 

关于 React+Redux(或者其它FLUX架构)就不在这里介绍太多了,有兴趣的可以参考相关的教程。

2、Rxjs

Rxjs 从诞生以来一直都不温不火,但它函数响应式编程(Functional Reactive Programming,FRP)的理念非常先进,虽然或许对于大部分应用环境来说,外部输入事件并不是太频繁,并不需要引入一个如此庞大的 FRP 体系,但我们也可以了解一下它有哪些优秀的特性。

在 Rxjs 中,所有的外部输入(用户输入、网络请求等等)都被视作一种 『事件流』:

--- 用户点击了按钮 --> 网络请求成功 --> 用户键盘输入 --> 某个定时事件发生 --> ......

举个最简单的例子,下面这段代码会监听点击事件,每 2 次点击事件产生一次事件响应:

1  
2 var clicks = Rx.Observable
3  
4 .fromEvent(document, ‘click‘)
5  
6 .bufferCount(2)
7  
8 .subscribe(x => console.log(x)); // 打印出前2次点击事件

 

其中 bufferCount 对于事件流的作用是这样的:

技术图片

是不是很神奇呢?Rxjs 非常适合游戏、编辑器这种外部输入极多的应用,比如有的游戏可能有『搓大招』这个功能,即监听用户一系列连续的键盘、鼠标输入,比如上上下下左右左右BABA,不用事件流的思想的话,实现会非常困难且不优雅,但用 Rxjs 的话,就只是维护一个定长队列的问题而已:

 1  
 2 var inputs = [];
 3  
 4 var clicks = Rx.Observable
 5  
 6 .fromEvent(document, ‘keydown‘)
 7  
 8 .scan((acc, cur) => 
 9  
10 acc.push(cur.keyCode);
11  
12 var start = acc.length - 12 < 0 ? 0 : acc.length - 12;
13  
14 return acc.slice(start);
15  
16 , inputs)
17  
18 .filter(x => x.join(‘,‘) == [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 66, 65].join(‘,‘))// 上上下下左右左右BABA,这里用了比较奇技淫巧的数组对比方法
19  
20 .subscribe(x => console.log(‘!!!!!!ACE!!!!!!‘));

 

当然,Rxjs 的作用远不止于此,但可以从这个范例里看出函数响应式编程的一些优良的特性。

 

三、总结

既然是完结篇,那我们来总结一下这三篇文章究竟讲了些啥?

第一篇文章里,介绍了纯函数、柯里化、Point Free、声明式代码和命令式代码的区别,你可能忘记得差不多了,但只要记住『函数对于外部状态的依赖是造成系统复杂性大大提高的主要原因』以及『让函数尽可能地纯净』就行了。

第二篇文章,或许是最没有也或许是最有干货的一篇,里面介绍了『容器』的概念和 Maybe、Either、IO 这三个强大的 Functor。是的,大多数人或许都没有机会在生产环境中自己去实现这样的玩具级 Functor,但通过了解它们的特性会让你产生对于函数式编程的意识。

软件工程上讲『没有银弹』,函数式编程同样也不是万能的,它与烂大街的 OOP 一样,只是一种编程范式而已。很多实际应用中是很难用函数式去表达的,选择 OOP 亦或是其它编程范式或许会更简单。但我们要注意到函数式编程的核心理念,如果说 OOP 降低复杂度是靠良好的封装、继承、多态以及接口定义的话,那么函数式编程就是通过纯函数以及它们的组合、柯里化、Functor 等技术来降低系统复杂度,而 React、Rxjs、Cycle.js 正是这种理念的代言人,这可能是大势所趋,也或许是昙花一现,但不妨碍我们去多掌握一种编程范式嘛

javascript函数式编程(代码片段)

JavaScript函数式编程(一) JavaScript函数式编程(二)在第二篇文章里,我们介绍了 Maybe、Either、IO 等几种常见的Functor,或许很多看完第二篇文章的人都会有疑惑:『这些东西有什么卵用?』事实上,如果只是为了学... 查看详情

javascriptes6函数式编程:柯里化偏应用组合管道(代码片段)

上一篇介绍了闭包和高阶函数,这是函数式编程的基础核心。这一篇来看看高阶函数的实战场景。首先强调两点:注意闭包的生成位置,清楚作用域链,知道闭包生成后缓存了哪些变量高阶函数思想:以变量作用域作为根基,以... 查看详情

从函数式编程到promise(代码片段)

...按:近年来,函数式语言的特性都被其它语言学过去了。JavaScript异步编程中大显神通的Promise,其实源自于函数式编程的Monad!原文:FunctionalComputationalThinking?—?Whatisamonad?译者:Fundebug为了保证可读性,本文采用意译而非直译。另外,... 查看详情

深入理解函数式编程(下)(代码片段)

...技术、重要特性和实践法则。在内容层面,主要使用JavaScript语言来描述函数式编程的特性,并以演算规则、语言特性、范式特性、副作用处理等方面作为切入点,通过大量演示示例来讲解这种编程范式。 查看详情

函数式编程(代码片段)

...到了广泛应用(Scala)How好了,进入正题以下示例代码均为JavaScript1.副作用--SideEffects先来看两段代码//代码片段1letminium=20;constcheckAge=(age)=>age>=minium;//代码片段2letnumber=2;constmultipleNumber=(n)=>number=number*n;returnnumber;这两段代码有问... 查看详情

用函数式编程,从0开发3d引擎和编辑器:函数式编程准备(代码片段)

...大部分人认为函数式编程差,主要基于下面的理由(参考JavaScript函数式编程存在性能问题么?):1)柯西化、函数组合等操作增加时间开销2)map、reduce等操作,会 查看详情

深入理解函数式编程(下)(代码片段)

...技术、重要特性和实践法则。在内容层面,主要使用JavaScript语言来描述函数式编程的特性,并以演算规则、语言特性、范式特性、副作用处理等方面作为切入点,通过 查看详情

javascript(代码片段)

JavaScript的介绍JavaScript(简称“JS”)是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript基于原型编... 查看详情

函数式编程--为什么要学习函数式编程?(代码片段)

函数式编程(FunctionalProgramming,FP)什么是函数式编程?通过纯函数来实现一些细粒度的函数,然后把这些细粒度的函数组合成功能更强大的函数,这一过程就是函数式编程,经典函数式编程库:lodash函数式编程是编程范式之一,... 查看详情

kotlin函数式编程①(函数式编程简介|高阶函数|函数类别|transform变换函数|过滤函数|合并函数|map变换函数|flatmap变换函数)(代码片段)

...动端开发与前端开发使用的技术趋于相同,在前端开发ES6(JavaScript标准ECMAScript6.0)中,也使用函数式编程;二、函数类别函数式编程中涉及到三种函数类别:变换Transform过滤Filter合并Combine在函数式编 查看详情

《javascript函数式编程思想》——从面向对象到函数式编程

...假如本书的写作时间倒退回十年前,书名可能会变成JavaScript面向对象编程思想。自上世纪90年代兴起的面向对象编程思想随Java的繁荣达于顶点,在JavaScript从一门只被用来编写零星的简单的表单验证代码的玩具语言变成日... 查看详情

函数式编程基本概念(代码片段)

编程范式命令式编程(Imperative)声明式编程(Declarative)函数式编程(Functional)面向对象、泛型、元编程也都是很主要的编程范式命令式编程:命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么声... 查看详情

函数式编程(代码片段)

编程的方法论:面向过程面向对象函数式 函数式编程的定义:函数式=编程语言定义的函数+数学意义上的函数(先想到一个数学模型,再用python上的功能实现这个逻辑) y=x+1defcat():returnx+1  特性:1.不用变量保存状... 查看详情

函数式编程简介-附入门方法(代码片段)

WHAT?什么是函数式编程?函数式编程是一种编程范式。编程范式又是什么?编程范式是一种解决问题的思路。我们熟悉的命令式编程把程序看作一系列改变状态的指令;而函数式编程把程序看作一系列数学函数映射的组合。编程... 查看详情

前端学习之函数式编程—函数式编程概念+头等函数(代码片段)

什么是函数式编程函数式编程(functionprogrammingFP)FP是编程范式之一,我们常听说的还有,面向过程编程,面向对象编程函数式编程的思维方式把现实世界的事物和事物之间的联系抽象到程序世界(对运算... 查看详情

textpython函数式编程(代码片段)

查看详情

函数式编程(代码片段)

编程的三种方法论:1面向过程2函数式3面向对象---------------------------------------------#把函数当做参数传给另一个函数deffoo(n):print(n)defbar(name):print(‘mynameis%s‘%name)foo(bar(‘alex‘))输出结果:mynameisalexNone------------------------# 查看详情

函数式编程(代码片段)

《Python从小白到大牛》第10章函数式编程定义函数函数参数使用关键字参数调用函数参数默认值可变参数函数返回值无返回值函数多返回值函数函数变量作用域生成器嵌套函数函数式编程基础函数类型上述代码第①行重构了calcula... 查看详情