函数式编程(四):函数组合、函子

函数组合

纯函数和柯里化很容易写出洋葱代码 h(g(f(x))),就是一个函数镶嵌另一个函数,多重组合,最终实现我们想要的功能,就像下面这个洋葱一样:
在这里插入图片描述
函数组合,就是利用多个函数,把细粒度的函数重新组合生成一个新的函数,从而实现我们想要的逻辑。

比如:获取数组的最后一个元素再转换成大写字母, .toUpper(.first(_.reverse(array)))

管道

下面这张图表示程序中使用函数处理数据的过程,给 fn 函数输入参数 a,返回结果 b。可以想想 a 数据通过一个管道得到了 b 数据:
在这里插入图片描述
当 fn 函数比较复杂的时候,我们可以把函数 fn 拆分成多个小函数,此时多了中间运算过程产生的 m 和 n。

下面这张图中可以想象成把 fn 这个管道拆分成了3个管道 f1, f2, f3,数据 a 通过管道 f3 得到结果 m,m再通过管道 f2 得到结果 n,n 通过管道 f1 得到最终结果 b:
在这里插入图片描述
代码形式简单表现为:

fn = compose(f1, f2, f3) 
b = fn(a)

如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数。

函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果。

函数组合默认是从右到左执行:

// 组合函数 
function compose(f, g) {
    return function (x) {
        return f(g(x))
    }
}
// 取首值
function first(arr) {
    return arr[0]
}
// 反转
function reverse(arr) {
    return arr.reverse()
}
// 从右到左运行 
let last = compose(first, reverse)
console.log(last([1, 2, 3, 4]))

函数的组合要满足结合律 (associativity):我们既可以把 g 和 h 组合,还可以把 f 和 g 组合,结果都是一样的:

// 结合律(associativity) 
let f = compose(f, g, h)
let associative = compose(compose(f, g), h) == compose(f, compose(g, h)) 
// true

Functor (函子)

到目前为止已经已经学习了函数式编程的一些基础,但是我们还没有演示在函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。

什么是 Functor

容器:包含值和值的变形关系(这个变形关系就是函数)。

函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运行一个函数对值进行处理(变形关系)。

Functor 函子

// 一个容器,包裹一个值 
class Container {
    // of 静态方法,可以省略 new 关键字创建对象 
    static of(value) {
        return new Container(value)
    }
    constructor(value) {
        this._value = value
    }
    // map 方法,传入变形关系,将容器里的每一个值映射到另一个容器 
    map(fn) {
        return Container.of(fn(this._value))
    }
}
// 测试
Container.of(3)
    .map(x => x + 2)
    .map(x => x * x)

总结

  1. 函数式编程的运算不直接操作值,而是由函子完成; 函子就是一个实现了 map 契约的对象;
  2. 我们可以把函子想象成一个盒子,这个盒子里封装了一个值;
  3. 想要处理盒子中的值,我们需要给盒子的 map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理;
  4. 最终 map 方法返回一个包含新值的盒子(函子)。

在 Functor 中如果我们传入 null 或 undefined:

// 值如果不小心传入了空值(副作用) 
Container.of(null).map(x => x.toUpperCase())
// TypeError: Cannot read property 'toUpperCase' of null

对错误进行处理,这就需要用到MayBe函子了。

MayBe 函子

  1. 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理;
  2. MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)。
class MayBe {
    static of(value) { 
        return new MayBe(value)
    }
    constructor(value) {
        this._value = value
    }
    // 如果对空值变形的话直接返回 值为 null 的函子 
    map(fn) {
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
    }
    isNothing() {
        return this._value === null || this._value === undefined
    }
}
// 传入具体值 
MayBe.of('Hello World')
    .map(x => x.toUpperCase())
// 传入 null 的情况
MayBe.of(null).map(x => x.toUpperCase())
// => MayBe { _value: null } 

MayBe 还是有个问题,就是我们很难确认是哪一步产生的空值问题,如下例:

MayBe.of('hello world')
    .map(x => x.toUpperCase())
    .map(x => null)
    .map(x => x.split(' '))
    // => MayBe { _value: null }

Either 函子

Either 两者中的任何一个,类似于 if…else…的处理,异常会让函数变的不纯,Either 函子可以用来做异常处理:

class Left {
    static of(value) {
        return new Left(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return this
    }
}

class Right {
    static of(value) {
        return new Right(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return Right.of(fn(this._value))
    }
}

Either 用来处理异常

function parseJSON(json) {
    try {
        return Right.of(JSON.parse(json));
    } catch (e) {
        return Left.of({ error: e.message });
    }
}
let r = parseJSON('{ "name": "zs" }')
    .map(x => x.name.toUpperCase())
console.log(r)

函子的思想,还需多多体会和应用锻炼。

文章内容输出来源:拉勾大前端高薪训练营,以上文章中的内容根据老师讲课的语音和代码,结合自己的理解编辑完成,请勿抄袭,转载请注明出处。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值