Js ES6函数式编程:函子

更新日期: 2019-10-09阅读: 2.2k标签: 编程

今天,我们来学习函数式编程的最后一个概念——函子(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函数式编程已经接近尾声。我们到底学到了什么?

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

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

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


链接: https://fly63.com/article/detial/6171

程序员的笔记,编程写软件学到的 7 件事

如果你真的做出了一些东西,在面对那些令人眼花缭乱的理论知识,或是和你相似甚至比你做的更糟糕的人时大可不必谦虚。在一天结束之时,正是那些在战壕中的开发者——构建、测试和开发了代码的人,真正做了事情。

自学编程的六个技巧总结

这些事情可以帮助新手在他们漫长的旅程中学习编程。我知道我还有更多东西需要学习,并将继续学习如何永远地学习。最重要的事情说三遍,请继续,不要放弃,不要放弃,不要放弃。

谈谈Javascript异步代码优化

Javascript代码异步执行的场景,比如ajax的调用、定时器的使用等,在这样的场景下也经常会出现这样那样匪夷所思的bug或者糟糕的代码片段,那么处理好你的Javascript异步代码成为了异步编程至关重要的前提

编程到底难在哪里?

以买苹果为例说明程序员如何解决问题。程序员需要对问题进行透彻的分析,理清其涉及的所有细节,预测可能发生的所有意外与非意外的情况,列出解决方案的所有步骤,以及对解决方案进行尽量全面的测试。而这些正是我认为编程难的地方。

Blockly - 来自Google的可视化编程工具

Google Blockly 是一款基于Web的、开源的、可视化程序编辑器。你可以通过拖拽块的形式快速构建程序,而这些所拖拽的每个块就是组成程序的基本单元。可视化编程完成

我真是受够编程了

成为伟大的程序员,需要付出许多编程之外的努力。我们的大脑是有限的,每天要应付的问题复杂到足以让人精神崩溃。当工作不顺利时,多少都会有些冒名顶替症候群的感觉。

前端的编程软件哪些比较好用?

推荐8款最好用的前端开发工具供美工或者前端开发人员使用,当然若你是NB的全栈工程师也可以下载使用。Web前端开发最常见的编程软件有以下几种: 在前端开发中,有一个非常好用的工具,Visual Studio Code,简称VS code

如何保持学习编程的动力

学编程现在看起来挺简单,因为网上有丰富的各种资源。然而当你实际去学的时候就发现,还是很难!对我来说也一样。但从某天起,我决定认认真真学编程一年。后来又过了一年,又过了一年又一年……我好像有点感悟。

编程小技巧

命名最好遵循驼峰法和下划线法,并且要清楚的表达变量的意思。相对于驼峰法而言,我更喜欢下划线法。下划线法可以更清楚的看出这个变量表示的意思。比如aBigGreenBanana和一个a_big_green_banana。

CSS并不是真正的编程语言

每隔几个月就会出现一篇文章表明:CSS并不是真正的编程语言。以编程语言的标准来说,CSS过于困难。使用这门语言会很有创造性:事实确实如此,CSS不同于传统的编程,且具有缺陷,同任何标准化编程语言相比

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!