函数式编程[4]functor和monad
Posted treeShaking
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数式编程[4]functor和monad相关的知识,希望对你有一定的参考价值。
compose遇到错误怎么办
上一节讲到compose,而compose天生pointFree,也就是数据会流淌过每个函数,这使得代码可读性大大增强。但有一个问题,如果中间某个函数出错了怎么办,我们当然不希望它继续执行下去,直接返回错误信息是稳妥的方案,比如compose(a, b, c), 这个compose组合函数可以看成最小的纯逻辑单元,数据首先流给c,函数c按照业务逻辑,遇到业务错误,需要直接退出。
我们引入盒子
给出函数式编程的解决方案,用一个网上的图吧:
现在我们要把数据装进盒子,每次经过函数做运算时,都要把数据从盒子里拿出,再做计算,再放回。这么做以后能解决分支问题。盒子的实现如下:
class Container {
public static of(v: any) {
return new Container(v);
}
// public value: any
// constructor(value: any) {
// this.value = value
// }
// 同下
constructor(public value: any) {
}
public map(f: any) {
return Container.of(f(this.value));
}
}
试玩一下这个盒子吧。
const a = 1
const result = Container.of(a).map((x: any) => 2 * x).value
console.log(result) // 2
盒子的作用
可以想到,map就是开盒子,取数据,做运算(map的参数),结果放回盒子的一系列操作。我们再玩一次。
const a = 1
const result = Container.of(a).map((x: any) => 2 * x).map((x: any) => x + 1).value
console.log(result) // 3
发现了什么?数据如同在compose中流淌一样。盒子和compose做的是一件事,我们来看一下更为严谨的盒子。
class Maybe {
public static of(v: any) {
return new Maybe(v);
}
constructor(public value: any) {
}
public isNothing() {
if (this.value) {
return false;
}
return true;
}
public map(f: any) {
return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.value));
}
}
let ret = Maybe.of(1).map((x: number) => x > 5 ? x : null).value
console.log(ret) // null
ret = Maybe.of(1).map((x: number) => x > 5 ? x : null).map((x: number) => 2 * x).value
console.log(ret) // null
你发现了吧,maybe的map会对盒子里的值做一个简单检查,如果是空那么就不执行map的回调,这就有点分支的苗头了,只是这个分支功能还不够强大。因为这是固定的判断,写死在map中,我们来搞个稍微厉害的分支。
export class Letf {
public static of = (x: any) => new Letf(x) // (x: any) => new IO(x) //
constructor(public value: any) {
}
public map = (f: (...a: any[]) => any) => this
}
export class Right {
public static of = (x: any) => new Right(x) // (x: any) => new IO(x) //
constructor(public value: any) {
}
public map = (f: (...a: any[]) => any) => Right.of(f(this.value))
}
import { Letf } from './letf';
import { Right } from './Right';
const fun = (data: any) => {
if (data < 100) {
return Letf.of('give me more money')
}
return Right.of(data)
}
let ret = fun(123).map((x: any) => 2 * x).value
console.log(ret) // 246
ret = fun(99).map((x: any) => 2 * x).value
console.log(ret) // give me more money
ret = fun(99).map((x: any) => 2 * x)
.map((x: any) => x + 1).value
console.log(ret) // give me more money
不过这个分支还不够完善,注意到此分支只能放在开头,你可以试一下放在map中会是什么样。
ret = fun(123).map((x: any) => 2 * x).map(fun).value
console.log(ret) // Right { value: 246, map: [Function] }
如上,最终value存的是right对象,why?因为map(fun)处要小心,fun返回一个left或者right对象,然后map会再创建一个right对象,其中value存fun的返回值。所以相当于将fun返回值(left/right对象)又用一个right对象包了一层,似乎就变得异常混乱了。
CHAIN / MONAD
要解开这个扣子很简单,我们增加一个解扣子函数吧。
export class Right {
public static of = (x: any) => new Right(x) // (x: any) => new IO(x) //
constructor(public value: any) {
}
public map = (f: (...a: any[]) => any) => Right.of(f(this.value))
public join = () => this.value
}
export class Letf {
public static of = (x: any) => new Letf(x) // (x: any) => new IO(x) //
constructor(public value: any) {
}
public map = (f: (...a: any[]) => any) => this
public join = () => this
}
运行下试试
ret = fun(123).map((x: any) => 2 * x)
.map(fun).join().value
console.log(ret) // 246
ret = fun(123).map((x: any) => 2 * x)
.map(fun).join()
.map((x: any) => x - 200)
.map(fun).join().value
console.log(ret) // give me more money
每当map(f) 试图多套一层时,我们就可以用join给他脱掉一层。我们试图用之前学的函数技巧让事情变得简单些
export class Right {
public static of = (x: any) => new Right(x) // (x: any) => new IO(x) //
constructor(public value: any) {
}
public map = (f: (...a: any[]) => any) => Right.of(f(this.value))
public join = () => this.value
public chain = (f: (...a: any[]) => any) => this.map(f).join()
}
export class Letf {
public static of = (x: any) => new Letf(x) // (x: any) => new IO(x) //
constructor(public value: any) {
}
public map = (f: (...a: any[]) => any) => this
public join = () => this
public chain = (f: (...a: any[]) => any) => this
}
再运行一下,观察区别
ret = fun(123).map((x: any) => 2 * x).chain(fun).value
console.log(ret) // 246
ret = fun(123).map((x: any) => 2 * x).chain(fun).map((x: any) => x - 200).chain(fun).value
console.log(ret) // give me more money
有了chain,再也不用担心漂亮姑娘多穿衣服了。而且如上面的代码,我们在中间并非只在开头使用了分支,这是我们真正想要的。忘了告诉你,map.join或者chain就是monad,还有我得提醒你的是,使用map.join或chain之后,你所使用的盒子已经被偷梁换柱了,这个盒子就是fun的返回值(也许你早就看明白了)。盒子可以解决逻辑中的出错处理的问题,如果你的逻辑中不存在出错的可能,如 (1+x)*y
,你完全可以用 compose(multiply(y),add(1))
来实现这个永远不会出错的运算逻辑。
目前我们都没涉及到副作用,异步等我们不愿意在纯函数中看到的东西,而他们是业务中不可避免的,我们下一节就啃它。
以上是关于函数式编程[4]functor和monad的主要内容,如果未能解决你的问题,请参考以下文章
进阶学习4:函数式编程FP——函子FunctorMayBe函子Either函子IO函子FolktalePointer函子Monad
高阶函数式编程:在 Kotlin 中“实现”单子(Monad)