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
}
实例必须满足下面三个规则:
-
自反性:
equals(x,x) === true
对于A
中的所有x
成立 -
交换性:
equals(x, y) === equals(y, x)
对于A
中所有x
,y
都成立 -
传递性: 对于
A
中的x
,y
,z
如果equals(x,y)===true
且equals(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
实例:给定一个A
的Eq
实例,和一个把B
转化为A
的函数,我们可以衍生出B
的Eq
实例
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