Functor函子01-MayBe & Pointed

2.9k words

在JavaScript中亦存在MayBe函子的设计,按F12在控制台尝试输入 new Array(6).map(v => console.log(v) 试试 console.log 是否执行了

🍐 函子即容器

在函数式编程中,函子是一个常见的概念。简单来说,函子是一个持有值的容器对象(通常通过类实现),其核心特性是具有map方法。map方法的作用是将一个函数应用到容器中的值,并返回一个新的函子对象,容器内部的值被映射为新值。这种设计使得我们可以方便地链式处理数据,且保持不可变性。

🍐 简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Container {
constructor(value) {
this.value = value
}

/** 接受一个函数,传入持有的值并执行返回一个新的函子 */
map(fn) {
return Container.of(fn(this.value))
}

/**
* 静态方法,接受一个值并返回新的函子容器
* 具有 of 静态方法的可以称为 Pointed 函子
* */
static of(value) {
return new this(value)
}
}

在上面的代码中,我们定义了一个Container类,它持有一个值value。类内部的map方法接受一个函数fn,将该函数应用到value上,最终返回一个新的Container对象。通过static of方法,我们可以将任意值包装成一个新的Container对象,从而形成函子

🍐 Pointed 函子

所谓Pointed函子,是指那些除了实现map方法外,还实现了of静态方法的函子。of方法允许我们从一个普通值创建一个函子。这样的函子不仅能进行map操作,还能从值的角度“指向”一个容器。因此,具备of方法的函子可以被称为Pointed

🌰 函子使用

1
2
3
4
5
6
7
const double = (n) => n * 2

const n = new Container(2)
.map(double) // Container { value: 4 }
.map(double) // Container { value: 8 }

console.log(n)

🍐 MayBe 函子:空值处理

MayBe函子是一个特殊的函子,它在普通函子的基础上增加了对空值的处理。我们可以用它来表示可能包含值,也可能为空的情形(如nullundefined)。通过MayBe函子,只有在容器中的值存在时,map方法才会执行操作,否则返回一个空的MayBe容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MayBe {
constructor(value) {
this.value = value
}

map(fn) {
return MayBe.of(this.isNothing() ? null : fn(this.value))
}

// 判断是否为空值
isNothing() {
return this.value === null || this.value === undefined
}

static of(value) {
return new this(value)
}
}

在这个实现中,MayBe容器有一个map方法,在执行map时,它首先会检查容器中的值是否为空。如果为空,map方法直接返回一个空的MayBe对象,否则将值传递给函数并返回一个新的MayBe容器。

🌰 MayBe函子 使用

1
2
3
4
5
6
7
const strVal = new MayBe('cool').map((v) => v.toUpperCase()) // MayBe { value: "COOL" }

// map 中的方法并没有并执行
const emptyVal = new MayBe(null).map((v) => v.toUpperCase()) // MayBe { value: null }

// 方法大胆用,只有存有值才会执行
new MayBe(null).map((v) => v.toUpperCase()).map((v) => `val: ${v}`)

上面的示例展示了如何使用MayBe函子来安全地处理可能为空的值。对于空值(如null),map方法会直接返回一个空容器。

🌰 MayBe函子:处理复杂数据

假设我们需要从一个API返回的数据中获取某个字段。这个数据可能会有不同的结构,包括空数据或缺失字段。我们可以利用MayBe函子链式地提取所需的值,从而避免重复的空值检查。

  • 情况1:预期数据
1
2
3
4
5
6
7
8
9
10
11
12
const result1 = {
data: {
id: 1,
title: '书名',
children: [
{ name: '章节1', id: 101 },
{ name: '章节2', id: 102 },
{ name: '章节3', id: 103 },
{ name: '章节4', id: 104 },
],
},
}
  • 情况2: children为空
1
2
3
4
5
6
7
const result2 = {
data: {
id: 2,
title: '书名',
children: [],
},
}
  • 情况3:获取到空数据
1
2
3
4
const result3 = {
data: {},
errorCode: '空空如也',
}

🌰 使用函子去获取最后一个章节的id

1
2
3
4
5
6
7
8
9
10
11
12
13
/** 获取最后一个章节的id */
const getLastChildId = (result) => {
return new MayBe(result)
.map((result) => result.data)
.map((data) => data.children)
.map((children) => children[children.length - 1])
.map((item) => item.id)
}

// 执行结果
const lastId1 = getLastChildId(result1) // MayBe { value: 104 }
const lastId2 = getLastChildId(result2) // MayBe { value: null }
const lastId3 = getLastChildId(result3) // MayBe { value: null }

在这个例子通过MayBe函子链式地从复杂的数据结构中提取最后一章的ID。无论数据是否完整或是否为空,可以避免重复检查nullundefined,让代码更加简洁和安全。

Comments