书接上回,Maybe
已经可以正确处理数据了。但是Maybe
遇到意外分支时只返回了null,开发者不清楚是哪个环节出现了问题。 所以需要一个函子来保留意外分支的上下文信息.
🍐 Either 函子
Either
相较与 Maybe
函子,保留了错误分支的上下文环境。Maybe
遇到错误分支时返回null
,而Either
会持有当前发生错误的分支信息。
🌰 Either 核心实现
Either
实现依靠Nothing
, Nothing
是一个不对持有值进行任何处理的函子。Maybe
函子上能正常执行的函数,在Nothing
不会执行。基于这个特性,在遇到意外分支时,即可使用 Nothing
保存当前的意外分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Nothing { constructor(value) { this.value = value }
map(fn) { return this }
static of(value) { return new this(value) } }
const val = new Nothing('hello').map((str) => `say: ${str}`)
|
🌰 Either 使用
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 27 28 29
| class Some { constructor(value) { this.value = value }
map(fn) { return Some.of(fn(this.value)) }
static of(value) { return new this(value) } }
const capitalCase = (val) => { try { return new Some(val).map((str) => `${str[0].toUpperCase()}${str.substring(1)}`) } catch (err) { return new Nothing(err) } }
console.log(capitalCase('hello').map((str) => `result: ${str}`))
console.log(capitalCase(undefined).map((str) => `result: ${str}`))
|
这里 Nothing
很好地捕获到了错误的位置。
🍐 Either 简单实现
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 27 28
| class Either { constructor(value) { this.value = value }
map(fn) { try { return Either.of(fn(this.value)) } catch (err) { return new Nothing(err) } }
static of(value) { return new this(value) } }
const capitalCase2 = (val) => { return new Either(val).map((str) => `${str[0].toUpperCase()}${str.substring(1)}`) }
console.log(capitalCase2('hello').map((str) => `result: ${str}`))
console.log(capitalCase2(undefined).map((str) => `result: ${str}`))
|
🍐 Monad 函子
具有 chain
方法的 Pointed
函子可称为 Monad
函子, 用于解决多重map
调用值嵌套问题。
! 多重map
调用
以下示例中,在第一个MayBe
函子的map
调用中返回一个新的函子做为值。这情况很常见在实际业务开发中,嵌套情况只会更复杂。
1 2 3 4 5 6
| const val = new MayBe('hello').map((str) => new MayBe(str).map((str) => str.toUpperCase()))
console.log(val)
console.log(val.value.value)
|
💡 使用 join
结果问题
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 27 28 29
| class MayBe { static of(value) { return new this(value) } constructor(value) { this.value = value } map(fn) { return MayBe.of(this.isNothing() ? null : fn(this.value)) } isNothing() { return this.value === null || this.value === undefined }
join() { return this.isNothing() ? MayBe.of(null) : this.value } }
const val = new MayBe('hello').map((str) => new MayBe(str).map((str) => str.toUpperCase()).join()) console.log(val)
const num = new MayBe(new MayBe(30).map((n) => n + 3)) console.log(num.join().map((n) => n + 200))
|
💡 使用 chain
join
可以解套单个函子的嵌套问题,但多个函子都要执行一次join
。这部分的逻辑可以封装到chain
方法中
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 27 28 29 30 31
| class MayBe { static of(value) { return new this(value) }
constructor(value) { this.value = value }
map(fn) { return MayBe.of(this.isNothing() ? null : fn(this.value)) }
isNothing() { return this.value === null || this.value === undefined }
join() { return this.isNothing() ? MayBe.of(null) : this.value }
chain(fn) { return this.map(fn).join() } }
const num = new MayBe(30).chain((n) => new MayBe(n + 3).map((n) => n + 200)) console.log(num)
|