原文: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。