前面二篇学习了函数式编程的基本概念和常见用法。今天,我们来学习函数式编程的最后一个概念——函子(Functor)。

相信有一部分同学对这个概念很陌生,毕竟现在已经有很多成熟的轮子,基本能满足我们日常的业务开发,所以没必须重复造轮子。但是,作为一名(未来)优秀的程序员,光会用怎么能行呢?必须要理解更深层的思想。下面就来学习函子部分的知识...

函子(Functor)

在正式学习函子之前,我会先抛出一个问题,先用普通的方式解决,然后转换为用函子解决,这能帮助我们更好的理解函子。同时,这也是我想说的,在我们学习一个新的知识点前,首先必须清楚为什么会有它,或者说它是为了解决什么问题而生的,这也是我们学习新知识后能够快速达到学以致用的最有效方法,不然很容易被遗忘。

function double (x) {
return x * 2
}
function add5 (x) {
return x + 5
} var a = add5(5)
double(a)
// 或者
double(add5(5))

我们现在想以数据为中心,串行的方法去执行,即:

(5).add5().double()

很明显,这样的串行调用清晰多了。下面我们就实现一个这样的串行调用:

要实现这样的串行调用,需要(5)必须是一个引用类型,因为需要挂载方法。同时,引用类型上要有可以调用的方法也必须返回一个引用类型,保证后面的串行调用。

class Num {
constructor (value) {
this.value = value ;
}
add5 () {
return new Num( this.value + 5)
}
double () {
return new Num( this.value * 2)
}
}
var num = new Num(5);
num.add5 ().double ()

我们通过new Num(5) ,创建了一个 num 类型的实例。把处理的值作为参数传了进去,从而改变了 this.value 的值。我们把这个对象返会出去,可以继续调用方法去处理数据。

通过上面的做法,我们已经实现了串行调用。但是,这样的调用很不灵活。如果我想再实现个减一的函数,还要再写到这个 Num 构造函数里。所以,我们需要思考如何把对数据处理这一层抽象出来,暴露到外面,让我们可以灵活传入任意函数。来看下面的做法:

class Num {
constructor (value) {
this.value = value ;
}
map (fn) {
return new Num( fn(this.value) )
}
}
var num = new Num(5);
num.map(add5).map(double)

我们创建了一个 map 方法,把处理数据的函数 fn 传了进去。这样我们就完美的实现了抽象,保证的灵活性。

到这里,我们的函子就该正式登场了。不用怕,其实函子的概念很简单,我们在上面其实已经创建了一个函子雏形。现在我们整理一下,创建一个真正的函子:

class Functor{
constructor (value) {
this.value = value ;
}
map (fn) {
return Functor.of(fn(this.value))
}
} Functor.of = function (val) {
return new Functor(val);
} Functor.of(5).map(add5).map(double)

现在我们可以用Functor.of(5).map(add5).map(double)去调用,是不是觉得清爽多了。

下面总结一下这个函子的几个特征:

  • Functor 是一个容器,它包含了值,就是this.value(想一想你最开始的new Num(5))
  • Functor 具有 map 方法。该方法将容器里面的每一个值,映射到另一个容器。(想一想你在里面是不是new Num(fn(this.value))
  • 函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器----函子。(想一想你是不是没直接去操作值)
  • 函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。(说的就是你传进去那个函数把 this.value 给处理了)
  • 函数式编程一般约定,函子有一个 of 方法,用来生成新的容器。(就是帮我们 new 了一个对象出来)

说了那么多,如果还是不理解函子概念的话,那也正常。因为仔细看看这也没什么的嘛,就是封装了一个简单的构造函数而已,咋就整出来一个新概念函子了呢?不理解不重要,主要是看到了函子帮我们更好的串行调用函数处理数据。回想一下我们上一节学的 compose,是不是很像呢?只是函子的调用方式显得更加优雅。

现在,我们已经认识了一个基础的函子。接下来,我们需要认识一个更加完善的函子——Maybe函子...

Maybe 函子

我们知道,在做字符串处理的时候,如果一个字符串是 null, 那么对它进行 toUpperCase() 就会报错。

Functor.of(null).map(value => value.toUpperCase())

所以我们需要对 null 值进行特殊过滤:

class Maybe{
constructor (value) {
this.value = value;
}
map (fn) {
return this.value ? Maybe.of(fn(this.value)) : Maybe.of(null);
}
}
Maybe.of = function (val) {
return new Maybe(val);
} var a = Maybe.of(null).map(function (s) {
return s.toUpperCase();
});

我们看到只需要把在中设置一个空值过滤,就可以完成这样一个 Maybe 函子。是不是so easy。

Monad 函子

Monad 函子也是一个函子,其实很原理简单,只不过在原有的基础上又加了一些功能。那我们来看看它与其它的 有什么不同吧。

我们知道,函子是可以嵌套函子的。比如下面这个例子:

function fn (e) { return e.value }

var a = Maybe.of( Maybe.of( Maybe.of('str') ) )
console.log(a);
console.log(a.map(fn));
console.log(a.map(fn).map(fn));

我们有时候会遇到一种情况,需要处理的数据是 Maybe {value: Maybe}。显然我们需要一层一层的解开。这样很麻烦,那么我们有没有什么办法得到里面的值呢?

class Monad {
constructor (value) {
this.value = value ;
}
map (fn) {
return this.value ? Maybe.of(fn(this.value)) : Maybe.of(null);
}
join ( ) {
return this.value;
}
}
Monad.of = function (val) {
return new Monad(val);
}

这样,我们就能很轻易的处理嵌套函子的问题了:

var  a = Monad.of( Monad.of('str') )
console.log(a.join().map(toUpperCase))

Modan函子也是一个很简单的概念,仅仅多了个 join 函数,为我们处理嵌套函子。

总结

至此,js函数式编程已经接近尾声。我们到底学到了什么?

首先,我们认识到了函数式编程的关注点是数据的映射关系,如何将一个数据结构更加优雅的转化为另一个数据结构。函数式编程的主体是纯函数,函数的内部实现不能影响到外部环境。

然后,我们学习了几个常用的函数式编程场景——柯里化、偏函数、组合和管道。 帮助我们更好的实际业务中运用函数式编程。

最后,我们运用函子实现了灵活的同步链式调用函数。

参考链接:在你身边你左右 --函数式编程别烦恼

JavaScript ES6函数式编程(三):函子的更多相关文章

  1. 《JavaScript ES6 函数式编程入门经典》--推荐指数⭐⭐⭐

    这本书比较基础认真看完再自己写点demo一个双休日就差不多, 总体来说看完还是有收获的,会激起一些你对函数编程的兴趣 主要目录如下: 第1章 函数式编程简介 11.1 什么是函数式编程?为何它重要 1 ...

  2. JavaScript ES6函数式编程(一):闭包与高阶函数

    函数式编程的历史 函数的第一原则是要小,第二原则则是要更小 -- ROBERT C. MARTIN 解释一下上面那句话,就是我们常说的一个函数只做一件事,比如:将字符串首字母和尾字母都改成大写,我们此 ...

  3. JavaScript ES6函数式编程(二):柯里化、偏应用和组合、管道

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

  4. Es6 函数式编程 MayBe函子的简单示例

    初级函子的作用非常简单,使用场景主要体现在:深入访问object的属性的时候,不会担心由于属性不存在.undefined.null等问题出现异常. MayBe.js var MayBe = funct ...

  5. 翻译连载 | 第 9 章:递归(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  6. 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 引言&前言

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 译者团队(排名不分先后):阿希.blueken.brucec ...

  7. 翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

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

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

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

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

随机推荐

  1. Oracle之select

    坚持

  2. C++基础之关联容器

    关联容器 关联容器和顺序容器的本质区别:关联容器是通过键存取和读取元素.顺序容器通过元素在容器中的位置顺序存储和访问元素.因此,关联容器不提供front.push_front.pop_front.ba ...

  3. Python学习笔记整理总结【Django】:Model操作(二)

    1.操作汇总 # 增 # # models.Tb1.objects.create(c1='xx', c2='oo') 增加一条数据,可以接受字典类型数据 **kwargs # obj = models ...

  4. Python学习笔记整理总结【ORM(SQLAlchemy)】

    一.介绍SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执 ...

  5. 从SpringMVC获取用户信息谈起

    Github地址:https://github.com/andyslin/spring-ext 编译.运行环境:JDK 8 + Maven 3 + IDEA + Lombok spring-boot: ...

  6. 在MacOS下使用sqlalchemy 连接sqlserver2012 数据库

    在MacOS下使用sqlalchemy 连接sqlserver 数据库 前言 最近有要求,要将数据库换成巨硬家的sqlserver 2012 因为在网上苦苦找不到sqlalchemy 配置连接SqlS ...

  7. 链表-LinkList

    什么是链表 维基百科:链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存 ...

  8. Circular RNA的产生机制、功能及RNA-seq数据鉴定方法

    推荐关注微信公众号:AIPuFuBio,和使用免费生物信息学资源和工具AIPuFu:http://www.aipufu.com. [Circular RNA的产生机制] Circular RNA,缩写 ...

  9. 基于 HTML5 和 WebGL 的地铁站 3D 可视化系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  10. SQL使用UPDATE和SUBSTRING截取字符串方法,从头截取到某个位置,截取中间片段,字符串中间截取到末尾或删除前面的字符串

    //从头截取 update 表名 set 表列名 =SUBSTRING(表列名,1,目标位置数值)  //!计数从1开始,从左往右 where 条件   //条件自己选择,不加where条件会更新所有 ...