fp-tsとEq
Kazuki Moriyama (森山 和樹)
値の等値性を司る型クラスEqの紹介。
https://gcanti.github.io/fp-ts/modules/Eq.ts.html
定義
export interface Eq<A> {
readonly equals: (x: A, y: A) => boolean
}
equalsという等値判断関数がいるだけ。
law
Eqが満たすべきlawは3つある。
- 反射律:
E.equals(a, a) === true
- 対称律:
E.equals(a, b) === E.equals(b, a)
- 推移律: E.equals(a, b) === trueかつE.equals(b, c) === trueならば
E.equals(a, c) === true
要するに要素は自分自身と等値で、順番を入れ替えて比較しても結果は同じで、ある要素と同じな別の要素同士を比較しても結果は同じである。
Eqのインスタンスを作成するときはこれらのlawを満たすようにしなければならない。
使い方
stringとかにはすでにインスタンスが用意されている。
import * as S from "fp-ts/string";
import * as Eq from "fp-ts/Eq";
console.log(S.Eq.equals("a", "a")); // => true
console.log(S.Eq.equals("a", "b")); // => false
コンビネータ
struct
オブジェクトに対するEqを別のEqから作成できる。
type Person = {
name: string;
age: number;
};
const personEq = Eq.struct<Person>({
name: S.Eq,
age: N.Eq,
});
console.log(
personEq.equals({ name: "taro", age: 20 }, { name: "taro", age: 20 })
); // => true
console.log(
personEq.equals({ name: "taro", age: 20 }, { name: "taro", age: 10 })
); // => false
tuple
タプルに対するEqを別のEqから作成できる。
const tuple3Eq = Eq.tuple(S.Eq, N.Eq, personEq);
console.log(
tuple3Eq.equals(
["a", 1, { name: "taro", age: 20 }],
["a", 1, { name: "taro", age: 20 }]
)
); // => true
console.log(
tuple3Eq.equals(
["a", 1, { name: "taro", age: 20 }],
["a", 1, { name: "taro", age: 10 }]
)
); // => false
もちろんタプルとして型が違うものを入れるとコンパイルエラーになる。
contramap
型Aに対するEqインスタンスとB => Aと型を変換する関数をとってEq<B>を作成する。
// numberをstringに変換したあとS.Eqで比較するEq<number>
const numEq = Eq.contramap((n: number) => n.toString())(S.Eq);
console.log(numEq.equals(1, 1)); // => true
console.log(numEq.equals(1, 2)); // => false
constructors
fromEquals
比較関数からEqを作成できる。
Eqインスタンスを作成するヘルパー関数。
const personAgeEq = Eq.fromEquals<Person>((a, b) => a.age === b.age);
// ageさえ同じなら同じPerson
console.log(
personAgeEq.equals({ name: "taro", age: 20 }, { name: "taro", age: 20 })
); // => true
console.log(
personAgeEq.equals({ name: "john", age: 20 }, { name: "taro", age: 20 })
); // => true
インスタンス
Contravariant
Eqに対してのContravariant。
console.log(
Eq.Contravariant.contramap(S.Eq, (a: number) => a.toString()).equals(1, 1)
); // => true
eqStrict
Eq<unkown>、つまりどの型に対しても使用できるEqインスタンス。
比較ロジックは===で決定される。
console.log(Eq.eqStrict.equals("1", 1)); // => false
getMonoid
Eq<A>のMonoidインスタンスを作成できる。
emptyは常にtrueを返すEq、concatは2つのEqを受け取って論理積を取るようなEqを返す。
console.log(
Eq.getMonoid<string>() // 最初の文字が同じで長さがが同じならば同じ文字列とみなす
.concat(
Eq.fromEquals<string>((a, b) => a[0] === b[0]),
Eq.fromEquals<string>((a, b) => a.length === b.length)
)
.equals("ac", "ab")
); // => true
// 常にtrueを返す
console.log(Eq.getMonoid<string>().empty.equals("a", "b")); => true
getSemigroup
getMonoidのemptyがなくなった版。