《The Joy of Javascript》- 2 - Functor/Monad
2022-06-20
一本书里面内容较多, 因此分成了多篇 Post, 可以从此处看到相关文章:
实现 FP 需要保证一些函数的输入和输出规范化. 以方便 compose 进行链式调用.
例如: arr.map(x=>x+1).map(x=>x+2)
就是因为 .map()
方法的输入输出的规范化以使得其可以链式调用, 多个 map 方法可以进行 compose: const testMethod = compose(map(capitalize), map(getAscii));
- A functor is anything (such as an object) that can be mapped over or that implements the map interface properly
- 函数式编程不直接操作值, 而是由函子完成
- 行为类似于一个容器, 容器会包裹不同的输入, 之后会返回一个统一的结构 (A Functor is a container which can be mapped upon by a Unary function.)
原生的 Array 类型就是一个 Functor
[1, 2, 3, 4].map(add).map(add)
- identity: 如果给 map 传递一个返回自己的方法, 那么得到的结果和自身相等
F.map(x=>x) === F
- composition: 多个方法可以进行链式调用, 比如下方的
f(g(x))
可以写成f.map(g).map(x)
F.map((x) => f(g(x))) === F.map(g).map(f)
一般会实现以下属性和方法:
1. _value
: 通过这个属性从容器中取得实际的值
2. of()
: 给方法提供初始值
3. map()
: map 方法接收一个 fn, fn 去得到一个新的值
class Container { static of(value) { return new Container(value) } constructor(value) { this._value = value } map(fn) { return Container.of(fn(this._value)) } } let c = Container.of(5) .map((x) => x * x) .map((x) => x + 2) console.log(c) // Container { _value: 27 }
const Functor = { map(f = identity) { /* 注意当前 Functor 需要用到 Container 的 get 方法 (因为 val 设置成了 private), 所有扩展了这个 Functor 的类都需要拥有这个 get 方法 */ return this.constructor.of(f(this.get())); }, }; const double = (x) => x * 2; const triple = (x) => x * 3; class Container { #val; /* 注意这个地方的 val 是 private, 因此必须使用 get 方法才能够得到对应的值. */ constructor(value) { this.#val = value; } static of(value) { return new Container(value); } get() { return this.#val; } } Object.assign(Container.prototype, Functor); console.log(Container.of(2).get()); /* 2 */ console.log(Container.of(2).map(double).get()); /* 4 */ console.log(Container.of(2).map(double).map(triple).get()); /* 12 */
class Maybe { static of(value) { return new Maybe(value); } constructor(value) { this._value = value; } map(fn) { /* 在这个地方进行了一些特殊的处理 */ return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this._value)); } isNothing() { return this._value === null || this._value === undefined; } } let obj = new Maybe(5) .map((x) => null) /* 此处对应的值已经转化成了 null */ .map((x) => x * x); console.log(obj);
- Monad 也是类似函子的概念, 区别在于有时候函子的返回值不是我们需要的类型
- 使用 compose 要求输入和输出一致比如
number -> number
- 但是有一些操作会返回更多的类型比如
number -> number[]
- 这个时候需要一个 flatMap 方法进行拍平
- 又或者可能会出现返回嵌套类型比如
number -> { res: number }
- 这个时候可能需要对 Object 进行额外的 mapping
- 使用 compose 要求输入和输出一致比如
- Monad 就是通过一个额外的方法来将返回不一致的情况进行处理, 使其方便后期 compose 复合操作
注意
chain
方法根据不同的实现可能会有不同的名称
- Left identity:
M.of(a).chain(f) === f(a)
- Right identity:
m.chain(M.of) === m
- Associativity:
m.chain(f).chain(g) === m.chain(x => f(x).chain(g))
/* Number -> Number[], 如果连续调用将会得到多层嵌套数组 */ var f = (x) => [x ** 2]; var g = (x) => [x * 3]; /* Monad Container */ var Container = class { of(value) { return new Container(value); } constructor(value) { this.value = value; } map(f) { return new Container(f(this.value)); } chain(f) { /* 此处对于 Functor 的 map 进行了一些额外操作 */ /* 这个方法根据需求不同会有不同实现, 比如对一些 side effect 的处理. */ return new Container(f(this.value).flat()[0]); } }; var test = new Container(2); // test.of(2).map(f).map(g); // 报错, Functor 的 map 方法无法处理数组 test.of(2).chain(f).chain(g); // Container { value: 12 }
const Functor = { map(f = identity) { return this.constructor.of(f(this.get())); }, }; /* 将 Functor 扩展到 Monad 中 */ const Monad = Object.assign({}, Functor, { flatMap(f) { return this.map(f).get(); }, }); /* 如此一来 SomeClass 就同时拥有了 map 和 flatMap, SomeClass 就成为了一个 Monad */ Object.assign(SomeClass.prototype, Functor, Monad);
- JavaScript offers behavior delegation via implicit links and mixins for building objects in a compositional manner.
- Behavior delegation is the natural way to model objects in JavaScript. It uses the implicit delegation mechanism present in JavaScript』s lookup process and the prototype chain.
- Object concatenation offers a simple approach based on structural object composition, which allows you to build objects by attaching (embedding) behavior from other independent objects.
- You can use mixins to extend objects (or classes) dynamically and favor structural composition over inheritance.
- Mixins address the issue of multiple inheritance through a mechanism known as mixin linearization.
- JavaScript offers a shortcut for Object.assign by using the spread operator, although Object.assign and the spread operator are not interchangeable.
关于本文
文章标题 | 《The Joy of Javascript》- 2 - Functor/Monad |
发布日期 | 2022-06-20 |
文章分类 | Reading |
相关标签 | #Reading #The Joy of Javascript |
留言板
PLACE_HOLDER
PLACE_HOLDER
PLACE_HOLDER
PLACE_HOLDER