原文:https://dev.to/gcanti/getting-started-with-fp-ts-setoid-39f3
这篇文章的标题是《Getting started with fp-ts: Eq》,由Giulio Canti在2019年3月12日发表,并在2021年10月27日更新。文章是关于如何在fp-ts
库中使用Eq
类型类的入门指南。
在这篇文章中,我将经常讨论“类型类”和“实例”,让我们看看它们是什么,以及它们如何在fp-ts
中编码。
"类型类"在维基百科上的定义是:
程序员通过指定一组函数或常量名称及其相应的类型,这些都必须存在于属于该类的每种类型上,来定义一个类型类。
在fp-ts
中,类型类被编码为TypeScript的interface
。
一个旨在包含承认等价的类型的类型类Eq
,声明如下:
interface Eq<A> {
/** 如果 `x` 等于 `y` 则返回 `true` */
readonly equals: (x: A, y: A) => boolean
}
声明可以这样理解:
如果类型
A
上定义了一个适当类型的名为equals
的函数,则类型A
属于类型类Eq
。
那么实例呢?
程序员可以通过实例声明,为任何类型
A
定义所有C
成员的实现,使其成为给定类型类C
的成员。
在fp-ts
中,实例被编码为静态字典。
例如,这是类型number
的Eq
实例:
const eqNumber: Eq<number> = {
equals: (x, y) => x === y
}
实例必须满足以下法则:
自反性:对于所有
A
中的x
,equals(x, x) === true
对称性:对于所有
A
中的x
,y
,equals(x, y) === equals(y, x)
传递性:如果
equals(x, y) === true
且equals(y, z) === true
,则对于所有A
中的x
,y
,z
,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/Eq
模块导出了一个getStructEq
组合器:
import { getStructEq } from 'fp-ts/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/Array'
const eqArrayOfPoints: Eq<Array<Point>> = getEq(eqPoint)
最后,构建Eq
实例的另一种有用方式是contramap
组合器:给定一个A
的Eq
实例和一个从B
到A
的函数,我们可以派生出B
的Eq
实例。
import { contramap } from 'fp-ts/Eq'
type User = {
userId: number
name: string
}
/** 如果两个用户的 `userId` 字段相等,则它们相等 */
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
下一篇文章将讨论Ord
。