FP TS Eq 等价关系 - kscarrot/blog GitHub Wiki

翻译自fp-ts-doc,自己记录用,侵删.

这个系列博客我想讨论一下关于"类型类"和"实例",让我们来看看在fp-ts中他们是什么样还有如何被描述的.

"类型类"在维基百科上的解释: 程序员通过指定一组函数或常量名称以及它们各自的类型来定义类型类,这些函数或常量名称对于属于该类的每种类型都必须存在。

fp-ts中,类型系统用TypeScript描述为interface S 一个类型类Eq,旨在包含一个描述相等关系的类型,其定义方式如下:

interface Eq<A> {
  /** returns `true` if `x` is equal to `y` */
  readonly equals: (x: A, y: A) => boolean
}

这个定义可以被解读为

如果一个 A 属于 类型类 Eq,那么就有一个被命名为equal的函数被定义在这个类型中 什么是实例? 程序员可以声明任意的A类型作为给定C类型的实例,通过在A中完成C类型中所有成员的实现. 在fp-ts中实例被描述成一个静态类型. 这里有一个number类型生成Eq实例的例子

const eqNumber: Eq<number> = {
  equals: (x, y) => x === y
}

实例必须满足下面三个规则:

  1. 自反性: equals(x,x) === true对于A中的所有x成立
  2. 交换性:equals(x, y) === equals(y, x)对于A中所有x,y都成立
  3. 传递性: 对于A中的x,y,z如果equals(x,y)===trueequals(y, z) === true,那么equals(x, z) === true

程序员可以通过下面的方式定义一个函数elem(这个命名代表他是集合中的一个元素):

function elem<A>(E: Eq<A>): (a: A, as: Array<A>) => boolean {
  return (a, as) => as.some(item => E.equals(item, a))
}

elem(eqNumber)(1, [1, 2, 3]) // true
elem(eqNumber)(4, [1, 2, 3]) // false

现在我们可以编写一些更加复杂的Eq类型的实例:

type Point = {
  x: number
  y: number
}

const eqPoint: Eq<Point> = {
  equals: (p1, p2) => p1.x === p2.x && p1.y === p2.y
}

我们也可以试着通过检查引用是否相等优化一下equals方法

const eqPoint: Eq<Point> = {
  equals: (p1, p2) => p1 === p2 || (p1.x === p2.x && p1.y === p2.y)
}

这是一个非常常见的模板代码.好消息是,如果如果我们可以为每个字段提供一个Eq实例,那么我们就能对整个Point构建一个Eq实例. 事实上我们在fp-ts/lit/Eq中导出了一个getEtructEq的结合子:

import { getStructEq } from 'fp-ts/lib/Eq'

const eqPoint: Eq<Point> = getStructEq({
  x: eqNumber,
  y: eqNumber
})

那么我们现在可以用过填充getStructEq来实现我们刚刚完成的定义

type Vector = {
  from: Point
  to: Point
}

const eqVector: Eq<Vector> = getStructEq({
  from: eqPoint,
  to: eqPoint
})

getStructEq不是fp-ts中导出的唯一的结合子,这里还有一个结合子允许我们完成一个数组类型的Eq实例

import { getEq } from 'fp-ts/lib/Array'
const eqArrayOfPoints: Eq<Array<Point>> = getEq(eqPoint)

最后还有一个更加通用的方法通过contramap这个结合子去构建一个Eq实例:给定一个AEq实例,和一个把B转化为A的函数,我们可以衍生出BEq实例

import { contramap } from 'fp-ts/lib/Eq'

type User = {
  userId: number
  name: string
}

/** two users are equal if their `userId` field is equal */
const eqUser = contramap((user: User) => user.userId)(eqNumber)

eqUser.equals({ userId: 1, name: 'Giulio' }, { userId: 1, name: 'Giulio Canti' }) // true
eqUser.equals({ userId: 1, name: 'Giulio' }, { userId: 2, name: 'Giulio' }) // false
⚠️ **GitHub.com Fallback** ⚠️