开始使用 fp-ts:Eq

2025-06-05

开始使用 fp-ts:Eq

在这个博客系列中,我会经常谈论“类型类”和“实例”,让我们看看它们是什么以及它们是如何编码的fp-ts

维基百科上的“类型类”

程序员通过指定一组函数或常量名称及其各自的类型来定义类型类,这些类型对于属于该类的每种类型都必须存在。

fp-ts类型类中,被编码为 TypeScript interface

类型类Eq旨在包含允许相等的类型,其声明方式如下

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

该声明可以解读为

如果在某个类型上定义了一个以相应类型命名的函数,则该类型A属于类型类Eqequal

那么实例呢?

程序员可以通过使用实例声明来使任何类型成为A给定类型类的成员,该实例声明定义了特定类型的C所有成员的实现CA

fp-ts实例中被编码为静态字典。

例如,这是Eq该类型的实例number

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

实例必须满足以下规律:

  1. 反身性equals(x, x) === true,对于x所有A
  2. 对称性equals(x, y) === equals(y, x)对于所有xyA
  3. 及物性:如果equals(x, y) === trueequals(y, z) === true,则equals(x, z) === true对于所有x,,yzA

然后,程序员可以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
Enter fullscreen mode Exit fullscreen mode

让我们Eq为更复杂的类型编写一些实例

type Point = {
  x: number
  y: number
}

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

我们甚至可以尝试equals通过首先检查引用相等性来优化

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

不过,这基本上只是样板代码。好消息是,我们可以Eq为结构体构建一个实例,就像为每个字段Point提供一个实例一样。Eq

事实上,该fp-ts/Eq模块导出了一个getStructEq 组合器

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

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

我们可以继续使用getStructEq刚刚定义的实例

type Vector = {
  from: Point
  to: Point
}

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

getStructEq不是提供的唯一组合器fp-ts,这里有一个允许Eq为数组派生实例的组合器

import { getEq } from 'fp-ts/Array'

const eqArrayOfPoints: Eq<Array<Point>> = getEq(eqPoint)
Enter fullscreen mode Exit fullscreen mode

最后,构建Eq实例的另一种有用方法是组合器:给定forcontramap的实例和从到 的函数,我们可以派生出for的实例EqABAEqB

import { contramap } from 'fp-ts/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
Enter fullscreen mode Exit fullscreen mode

下一篇订单

文章来源:https://dev.to/gcanti/getting-started-with-fp-ts-setoid-39f3
PREV
使用 Typescript Mixins 组合 Angular 组件 优先选择组合而不是继承
NEXT
fp-ts入门:半群