chengaofeng
发布于 2024-10-12 / 10 阅读
0
0

Getting started with fp-ts: Eq

原文: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中,实例被编码为静态字典。

例如,这是类型numberEq实例:

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

实例必须满足以下法则:

  1. 自反性:对于所有A中的xequals(x, x) === true

  2. 对称性:对于所有A中的xyequals(x, y) === equals(y, x)

  3. 传递性:如果equals(x, y) === trueequals(y, z) === true,则对于所有A中的xyzequals(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组合器:给定一个AEq实例和一个从BA的函数,我们可以派生出BEq实例。

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


评论