fp-ts を使ってみながら関数型指向の理解を進める(Eq 編)

# fp-ts を使ってみながら関数型指向の理解を進める(Eq 編)

## Eq とは

[ライブラリのドキュメント](https://gcanti.github.io/fp-ts/modules/Eq.ts.html#eq-interface)から、Eq のインターフェースは以下の通り。型`A`をジェネリクスで受け取り、`equals`プロパティを持つオブジェクトである。
`equal`プロパティはジェネリクスで受け取った型`A`の引数`x`、`y`を受け取り boolean を返す関数であることが分かる。

```ts
export interface Eq<A> {
  readonly equals: (x: A, y: A) => boolean;
}
```

Eq は、集合 A 内の要素に対して等価であることはどういうことかを定義するものである。そして、この等価性を`equals`メソッドを実行することにより判定する。すなわち以下を満たす関数$f(x,y)$を`equals`プロパティに設定するのである。

```math
\forall x, \forall y \in A \\
f(x, y) = \left\{
\begin{array}{ll}
\mathrm{true} & (x = y) \\
\mathrm{false} & (x \neq y)
\end{array}
\right.
```

誤解を恐れずに言えば集合`A`は TypeScript の型に相当すると考えて問題ない。
Eq のインターフェースが分かったところで、具体的な集合`A`を考えて Eq のインターフェースを実装に使ってみる。
これ以降のコードで使っている Eq モジュールは以下のようにインポートされてあることを前提としている。

```ts
import { fromEquals, struct, Eq, tuple, contramap } from 'fp-ts/lib/Eq';
```

## `A = number`のとき

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

そして、この Eq は以下のように等価性の判定に使用できる。

```ts
expect(eqNumber.equals(1, 1)).toBe(true);
expect(eqNumber.equals(1, 2)).toBe(false);
```

実はよく使う型に対しては標準の Eq が用意されてあるので、今回実装した `eqNumber`は用意されてあるものを使えば良い。他にも string 型や boolean 型に対してデフォルトの Eq が用意されてある。

```ts
import * as N from 'fp-ts/number';
import * as S from 'fp-ts/string';
import * as B from 'fp-ts/boolean';

expect(N.Eq.equals(1, 1)).toBe(true);
expect(N.Eq.equals(1, 2)).toBe(false);

expect(S.Eq.equals('a', 'a')).toBe(true);
expect(S.Eq.equals('a', 'b')).toBe(false);

expect(B.Eq.equals(true, true)).toBe(true);
expect(B.Eq.equals(true, false)).toBe(false);
```

## `A = Point`のとき

次に`equals`関数が`===`ではない場合を考えてみよう
ここで 2 次元座標平面上の点を表す`Point`型をについて、`eqPoint`を実装してみることを考えてみよう
ここで考える`Point`の型は以下の通り

```ts
export type Point = {
  x: number;
  y: number;
};
```

`Point`型の変数`p1`、`p2`が等しいとは、`x`、`y`フィールドのそれぞれの値が等しいことと考えると、`eqPoint`は以下のように実装できる。(ジェネリクスで`Point`型を渡すことで`equals`メソッドの引数`p1`、`p2`に対して型推論が効く)

```ts
const eqPoint: Eq<Point> = {
  equals: (p1, p2) => {
    return p1.x === p2.x && p1.y === p2.y;
  },
};
```

実装した`eqPoint`の使い方は以下の通り

```ts
const p1: Point = { x: 1, y: 2 };
const p2: Point = { x: 1, y: 2 };
const p3: Point = { x: 0, y: 0 };

expect(eqPoint.equals(p1, p2)).toBe(true);
expect(eqPoint.equals(p1, p3)).toBe(false);
```

## Eq モジュールに含まれるその他の使い方

### fromEquals

`fromEquals`は`equals`関数から Eq を作る関数

```ts
const equal = (p1: Point, p2: Point) => {
  return p1.x === p2.x && p1.y === p2.y;
};
const eqPoint: Eq<Point> = fromEquals(equal);

expect(eqPoint.equals(p1, p2)).toBe(true);
expect(eqPoint.equals(p1, p3)).toBe(false);
```

### contramap

`contramap`の型シグネチャは以下の通り

```ts
export declare const contramap: <A, B>(f: (b: B) => A) => (fa: Eq<A>) => Eq<B>;
```

集合$A,B$に対して、$B$から$A$に対しての写像$f:B \rightarrow A$と、$A$に対しての等価判定`eq<A>`を入力とし、$B$に対しての等価判定`Eq<B>`を作る関数
ドキュメントにある例であるが、以下ような`User`型の一致判定を`id`プロパティの比較で行う`Eq<User>`を作りたい場合を考える。

```ts
// 分かりやすいようにあえてId型を定義した。
type Id = string;

type User = {
  id: Id;
  name: string;
  age: number;
};
```

以下のように、今、手元に持っているのは`Id`型に対する等価を表す`eqId:Eq<Id>`と`User`型から`Id`型への変換関数`userToIdFunc`であるとする。

:::note info
先程の集合$A$が`Id`型に、集合$B$が`User`型に、写像$f:B \rightarrow A$が`userToIdFunc`関数に対応している。
:::

```ts
const eqId: Eq<Id> = S.Eq;
const userToIdFunc: (user: User) => Id = (user) => user.id;
```

`eqId`と`userToIdFunc`を使って`Eq<User>`を以下のように作ることができる。

```ts
const eqUser: Eq<User> = contramap(userToIdFunc)(eqId);
```

また、`pipe`を使えば以下のようにも書ける。`pipe`については`function`モジュールの記事で説明しますが、比較すれば、`pipe`の使い方は理解してもらえるかと思う。

```ts
const eqUserWithPipe: Eq<User> = pipe(eqId, contramap(userToIdFunc));
```

`Eq<User>`も他の Eq と同様に使うことができる。

```ts
import { pipe } from 'fp-ts/function';
const user1: User = { id: 'b', name: 'Bob', age: 20 };
const user2: User = { id: 'b', name: 'Bobby', age: 20 };
const user3: User = { id: 'a', name: 'Alice', age: 23 };

expect(eqUser.equals(user1, user2)).toBe(true);
expect(eqUser.equals(user1, user3)).toBe(false);

expect(eqUserWithPipe.equals(user1, user2)).toBe(true);
expect(eqUserWithPipe.equals(user1, user3)).toBe(false);
```

### struct

オブジェクトのプロパティそれぞれに対して Eq を作り、そこから、オブジェクト全体に対しての Eq を作成する関数。

```ts
// プロパティそれぞれのEq
const eqNumber = (n1: number, n2: number) => n1 === n2;

// オブジェクト全体のEq
// 各プロパティにプロパティそれぞれのEqそ設定してやる
const eqPoint = struct({
  x: fromEquals(eqNumber),
  y: fromEquals(eqNumber),
});

expect(eqPoint.equals(p1, p2)).toBe(true);
expect(eqPoint.equals(p1, p3)).toBe(false);
```

### tuple

`struct`の配列バージョン。配列のそれぞれのインデックスに対して、Eq を作り、そこから、配列全体に対しての Eq を作成する関数。

```ts
const eqTuple = tuple(N.Eq, S.Eq, B.Eq);

expect(eqTuple.equals([1, '1', true], [1, '1', true])).toBe(true);
expect(eqTuple.equals([1, '1', true], [2, '1', true])).toBe(false);
```