《The Joy of Javascript》阅读笔记2 - Functor/Monad

最初发布于 szhshp的第三边境研究所, 转载请注明

相关文章

一本书里面内容较多, 因此分成了多篇 Post, 可以从此处看到相关文章:

Functor & Monad | 函子和单子

实现 FP 需要保证一些函数的输入和输出规范化. 以方便 compose 进行链式调用.

例如: arr.map(x=>x+1).map(x=>x+2) 就是因为 .map() 方法的输入输出的规范化以使得其可以链式调用, 多个 map 方法可以进行 compose: const testMethod = compose(map(capitalize), map(getAscii));

Functor

Functor 的特性

  1. A functor is anything (such as an object) that can be mapped over or that implements the map interface properly
  2. 函数式编程不直接操作值, 而是由函子完成
  3. 行为类似于一个容器, 容器会包裹不同的输入, 之后会返回一个统一的结构 (A Functor is a container which can be mapped upon by a Unary function.)

原生的 Array 类型就是一个 Functor

[1, 2, 3, 4].map(add).map(add)  

Functor 的设计原则

  1. identity: 如果给 map 传递一个返回自己的方法, 那么得到的结果和自身相等
    • F.map(x=>x) === F
  2. composition: 多个方法可以进行链式调用, 比如下方的 f(g(x)) 可以写成 f.map(g).map(x)
    • F.map((x) => f(g(x))) === F.map(g).map(f)

Functor 实例

一般会实现以下属性和方法:
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 }

More Functor Example

Functor with Mixin
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 */
Maybe Functor
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

Monad 的特性

  • Monad 也是类似函子的概念, 区别在于有时候函子的返回值不是我们需要的类型
    • 使用 compose 要求输入和输出一致比如 number -> number
    • 但是有一些操作会返回更多的类型比如 number -> number[]
      • 这个时候需要一个 flatMap 方法进行拍平
    • 又或者可能会出现返回嵌套类型比如 number -> { res: number }
      • 这个时候可能需要对 Object 进行额外的 mapping
  • Monad 就是通过一个额外的方法来将返回不一致的情况进行处理, 使其方便后期 compose 复合操作

Monad 的设计原则

注意 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))

Monad 实例

/* 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 } 

Interoperation with Class | 给 Class 扩展 Functor 和 Monad

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);

Summary

  • 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.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容