- CATALOG -
Typescript 中的类型类 (2)
在 TypeScript 中实现一个简单的类型类

类型类Monoid

Fp-ts中的Monoid类型类用于抽象如何结合两个值。

它内部只有两个成员:

interface Monoid<A> {
  empty: A;
  concat: (x: A, y: A) => A;
}

concat封装了结合两个值的逻辑,empty是一个用于表示同样类型空值的值。我们可以写一个Monoid<number>的实例来展示一下用加法结合两个数字。

const addition: Monoid<number> = {
  empty: 0,
  concat: (a, b) => a + b
}

addition.concat(4, 8) // 12

我们还能用这个做些什么呢?让我们研究一下另外一个fp-ts中很有用的方法foldMap

假设你有一组苹果,并且你有办法把两个苹果结合成一个苹果。有了这两样东西,你应该可以把这组苹果“压缩”成一个苹果:只需要把每个苹果都和它前边那个苹果结合起来。

再假设你有一组香蕉,并且你有办法把两个橘子组合成一个橘子。那么你还需要什么才能做到:把一组香蕉组合成一个橘子?如果你有办法可以把香蕉变成橘子,那么你只要把香蕉迭代一遍,把每一个香蕉都变成橘子,然后再通过橘子结合大法把它们变成一个橘子。

这就是foldMap背后的运作模式。而且foldMap是具有普适性的,这意味着它可以作用于一组任何东西上,A和任何变成B的方法(Monoid<B>)。

下边是foldMap函数的签名:

foldMap: <B>(M: Monoid<B>) => <A>(f: (a: A) => B) => (fa: A[]) => B

这个函数可以切分成三个部分理解:

<B>(M: Monoid<B>),返回是<A>(f: (a: A) => B),这个函数的返回值则是(fa: A[]) => B

总的来说,这个函数允许我们传入一个类型为A的数组,一个类型为B的monoid,还有一个函数可以把A转换B,并且最后返回一个单独的B。说起来很拗口,我们来试着用这个函数来统计一下所有用户的登陆次数。

import { Monoid } from "fp-ts/lib/Monoid";
import { foldMap } from "fp-ts/lib/Array";

type User = {name: string, logins: number}

const users: User[] = [
  { name: "Paul", logins: 10 },
  { name: "Sue", logins: 7 },
  { name: "Bob", logins: 8 }
];

foldMap(addition)((u: User) => u.logins)(users); // 25

foldMap允许我们把值通过Monoid中的”转换“进行”转换“。如果这个列表里没有元素,foldMap就直接使用Monoid中的zero值。

这个类似lodash中的sumBy:

_.sumBy(users, u => u.logins)

不同之处在于我们可以用任何Monoid去组合这些值,而不是只能用加法Monoid。如果你想要计算这些列表里的用户哪些是频繁使用的用户要怎么办?如果一个用户登陆至少5次我们就认为他是频繁用户,因此我们需要把数组中的元素压缩一下。

首先,我们把数组从用户对象映射为boolean(小于5次返回false,否则是true),然后通过用一个操作boolean值的monoid来结合这些boolean值:

declare const andMonoid: Monoid<Boolean>;
const userIsFrequent = (u: User) => u.logins > 4;
foldMap(andMonoid)(userIsFrequent)(users); // true

andMonoid的实现:

const andMonoid: Monoid<Boolean> = {
  empty: true,
  concat: (a, b) => a && b
}

那么foldMap内部实现是如何呢?

const myFoldMap: <B>(M: Monoid<B>) => <A>(f: (a: A) => B) => (as: A[]) => B =
  M => fab => as =>
    as.reduce((b, a) => {
      return M.concat(b, fab(a))
    }, M.empty)

Last modified on 2021-02-20