JavaScript函数式编程的一些理解

最近在看JavaScript函数式编程的部分,有些阅读之后的心得和理解,我想总结并分享出来。

首先是什么是函数式编程?函数式编程实际上与过程式编程一样,是一种编程的风格。函数式编程是以函数作为第一公民。

提到函数式,必须理解下面几个概念:

 

1. 纯函数

 纯函数是指一个输出对应一个输入,且没有任何副作用的函数

如果理解没有任何副作用呢?就是和函数外部没有任何联系。像IO操作,文件读取这一类就不是纯函数。

 

var fs = require('fs');

function readFile(filename) {
    fs.readFile(filename, function(err, data) {
       //...
    });
}

 像这里的readFile函数就不是一个纯函数,因为里面涉及文件读取操作。

那么纯函数有什么用呢?函数式编程与纯函数又有什么关联?

这时候我们需要了解范畴学的概念。

 

2. 范畴学

范畴是数学上的一个概念,通俗点来讲,范畴是一个集合,是由许多个值以及它们之间的关联所组成,其中每个值可以通过关联变化成另外一个值。将这个知识迁移到函数式编程,这个关联就是函数。

所以函数式编程实际上是由一个值,通过函数来变化为另一个值。是一个求值的运算,那就意味着函数必须是纯函数,不能有其他的副作用,否则就不符合函数式编程的原则了。

 

了解了范畴学之后,我们再来接触一下柯里化。

 

3. 柯里化

什么是柯里化?就是将一部分参数传递给函数进行调用,并返回一个函数处理剩下的参数。

举个简单的例子:

function add(x, y) {
    return x + y;
}

改为柯里化的写法,就是:

function add(x) {
    return function(y) {
        return x + y;
    }
}

 

那么为什么要柯里化,这样写有什么好处?这里先留个悬念,等后面讲完组合我再解释。

 

4. 组合(compose)

在函数式编程里,组合是很重要的一个概念,它可以将多个不同功能的纯函数给结合起来,实现一种类似管道的功能。

比如,

function g(x) {
    //...
}

function f(x) {
    //...
}

f(g(a));

a要经过g函数和f函数的处理才能得到最终的值,那么我们可以像上面那样写,而在函数式编程中,通过一个compose将他们结合起来。

 

function compose(f, g) {
    return function(x) {
        return f(g(x));
    }
};

 

介绍完compose,就可以解答为什么函数式编程需要柯里化了。因为compose中的函数是处理单参数,像f(x)、g(x),如果是f(a, b, c)和g(y, z)就不能这样处理了。所以需要用柯里化将函数转换为单参数函数。

 

讲完了基本的概念,下面来介绍一个叫函子(Functor)的东西

 

函子是用来将一个范畴转换到另一个范畴。

比如原本有一个姓名的范畴集合,通过将某个转换函数传入函子,将它转换成各自最喜欢科目的一个范畴。

 

下面来介绍函子的两个常用的功能:

 

1. map

这个操作可以将一个范畴的值转换到另一个范畴中,同时返回这个新容器。

晒上代码:

 

class Functor {
    constructor(v) {
        this.__value = v;
    }

    map(f) {
        return new Functor(f(this.__value));
    }
}

 

2. of

创建一个新容器的时候一般用new进行构造调用,可是new是来自面向对象的编程,不适用于函数式编程,所以有了of方法。

Functor.of = function(v) {
    return new Functor(v);
};

 

接下来介绍不同种类的函子:

 

1. Maybe函子

Maybe函子是为了解决可能传入空值的情况。

class Maybe extends Functor {
    map(f) {
        return this.__value ? Maybe.of(f(this.__value)) : Maybe.of(null);
    }
}

这样可以应用在以下的情况

Maybe.of(null).map(function(x) {
    console.log(x);
});

2. Either函子

有两个值Right和Left,Right是正常情况下用的值,而Left是Right不存在时采用的默认值

class Either extends Functor {
    constructor(l, r) {
        super();
        this.left = l;
        this.right = r;
    }

    map(f) {
        return this.right ? 
               Either.of(this.left, f(this.right)) :
               Either.of(f(this.left), this.right);
    }
}

 

3. ap函子

函子里面包含的值,也有可能是函数,不一定是数值。这时候就用到ap

 

class Ap extends Functor {
    ap(F) {
        return Ap.of(this.__value(F.value));
    }
}

用ap函子,可以实现对多参数的函数进行链式调用

例如:

function add(x) {
    return function(y) {
        return x + y;
    } 
}

Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));

 

4. Monad函子

最后,函子之后可能会出现函子,变成了多层的嵌套。这也是合法的。

如果按照之前的操作,当出现嵌套的时候取值要一直调用this.__value,而Monad提供了一个叫flatMap的方法,可以保证在

嵌套的情况下,永远取出最里面的值。将这个嵌套的函子铺平。

 

class Monad extends Functor {
  join() {
    return this.val;
  }

  flatMap(f) {
    return this.map(f).join();
  }
}

join方法保证了flatMap方法总是返回一个单层的函子

 

 

参考文章:

1. 函数式编程入门教程  ---- 阮一峰

2. JS 函数式编程指南

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值