JavaScript 函数式编程04-函子

函子是范畴论中的个概念,它把一个范畴中的对象映成另一个范畴的对象,把一个范畴中的态射映成另一个范畴中的态射,形象理解为范畴之间的“映射”。

简单来说,函子是某种映射关系。函子Functor 在 JavaScript 里主要是将某些值或对象包含起来并实现对应的一些函数来处理这些数值或对象的函数,函子必须实现map函数,其在遍历对象值的时候返回一个新对象。

换句话说,函子是实现了map的对象。举个例子:

1
2
3
4
5
6
7
8
9
const Box = function(val) {
this.val = val;
}
Box.of = function(val) {
return new Box(val);
}
Box.prototype.map = function(func) { // 必须
return Box.of(func(this.val));
}

注意,以上实现中 of 的实现不是必要的,of 的实现可以让使用者不必使用 new 来新建对象,而是使用 .of() 来实现新建对象:

1
2
3
4
5
6
7
8
let time = Box.of("2022-03-14");

time.map(i => i + ' 15').map(i => i + ':22')
// =>
// Box {
// val: '2022-03-14 15:22',
// __proto__: { map: ƒ (), constructor: ƒ Box() }
//}

实现了 of 的函子被称为 Pointed函子

在函数式编程中,函子一般用于错误处理!下面介绍几个常见的函子:

MayBe函子

MayBe函子以函数式的方式处理错误,提供检查 nullundefined 的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const MayBe = function(val) {
this.val = val;
}
MayBe.of = function(val) {
return new MayBe(val);
}
MayBe.prototype.isNull = function() {
return this.val == null; // 两个等于号匹配null和undefined
}
MayBe.prototype.map = function(func) {
return this.isNull() ? MayBe.of(null) : MayBe.of(func(this.val));
}

let time = MayBe.of("2022-03-14");
.map(i => i + ' 15')
.map(i => undefined) // 模拟出错,结果返回null,过程中不会报错
.map(i => i + ':22')
// =>
// MayBe {
// val: null,
// __proto__: {
// isNull: ƒ (),
// map: ƒ (),
// constructor: ƒ MayBe()
// }
//}

Option函子

Option函子MayBe函子 进化版,MayBe函子并不能确定是在哪里值变为 null 的,而 Option函子 能够解决分支拓展问题。

Option函子主要解决正常和出错两种状态,所以需要两个分支,即需要两个函子。一种实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Nothing = function(val) {
this.val = val;
}
Nothing.of = function(val) {
return new Nothing(val);
}
Nothing.prototype.map = function(func) {
return this;
}

const Some = function(val) {
this.val = val;
}
Some.of = function(val) {
return new Some(val);
}
Some.prototype.map = function(func) {
return Some.of(func(this.val));
}

const Option = {
Nothing: Nothing,
Some: Some
}

使用时可将其封装在函数内,使用 try ... catch 来捕获错误,如果出现错误就使用 Nothing 将错误信息包裹,如果无错误就用 Some 包裹。

1
2
3
4
5
6
7
8
9
let ans;
try {
ans = Some.of(5);
throw "a is less 5." // 模拟错误
} catch (e) {
ans = Nothing.of(e)
}

ans.map(i => i + 1).map(i => i % 2)

在 Rust 或 Java 等语言中也是有类似的功能。

Monad

Monad 实际上也是一个特殊的函子,它只是实现了 chain 的函子!

首先,给出Monad的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Monad = function(val) {
this.val = val;
}
Monad.of = function(val) {
return new Monad(val);
}
Monad.prototype.isNull = function() {
return this.val == null;
}
Monad.prototype.map = function(func) {
return this.isNull() ? Monad.of(null) : Monad.of(func(this.val));
}
// join
Monad.prototype.join = function() {
return this.isNull() ? Monad.of(null) : this.val;
}
// chain
Monad.prototype.chain = function(func) {
return this.map(func).join();
}

首先举个例子:有一个API请求,将其返回的结果用函子包裹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fetch("https://api.xygeng.cn/day").then(t => t.json())
.then(data => {
let ans = Monad.of(data)
.map(d => d.data)
// =>
// {
// date: '2023-3-14',
// today: true,
// leap: false,
// sign: '双鱼座',
// solar: { ... },
// lunar: { ... },
// festival: []
// }
})
})

需求是将数据整合进一个数组,返回数组也使用Monad包裹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fetch("https://api.xygeng.cn/day").then(t => t.json())
.then(data => {
let ans = Monad.of(data)
.map(d => d.data)
.map(d => {
console.log(d)
let arr = [];
for (p in d) {
arr.push([p, d[p]]);
}
return Monad.of(arr); // 注意,返回数组也使用Monad包裹
})
console.log(ans)
})
})

此时,返回的结果会有嵌套的 Monad,这时候需要借助 join 函数来展开。

1
2
3
Monad.prototype.join = function() {
return this.isNull() ? Monad.of(null) : this.val;
}

使用方法为在 map 后面加个 joinmap().join() 。而 chian 实际上就是该写法的简略版!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fetch("https://api.xygeng.cn/day").then(t => t.json())
.then(data => {
let ans = Monad.of(data)
.map(d => d.data)
// chain
.chain(d => {
let arr = [];
for (p in d) {
arr.push([p, d[p]]);
}
return Monad.of(arr);
})
})
})

所以,Monad 只是实现了 chain 的一个特殊函子!

JavaScript 函数式编程
作者:Kart Jim
链接:https://github.com/can-dy-jack/delicate/2023/02/21/js函数式编程/JavaScript 函数式编程04-函子/
来源:Hexo
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
长风破浪会有时,直挂云帆济沧海