fp-tsとOrd
順番を司る型クラスOrdの紹介。
https://gcanti.github.io/fp-ts/modules/Ord.ts.html
定義
export interface Ord<A> extends Eq<A> {
readonly compare: (first: A, second: A) => Ordering
}
compareという比較用の関数が一つだけ生えている。
compareが返すOrderingは比較結果を表す-1、0、1のどれかである。
firstがsecondよりも小さい場合は-1を、等値な場合は0を、大きい場合は1を返す。
使い方
import * as Ord from "fp-ts/Ord";
import * as S from "fp-ts/string";
import * as N from "fp-ts/number";
console.log(N.Ord.compare(1, 2)); // => -1
console.log(N.Ord.compare(1, 1)); // => 0
console.log(S.Ord.compare("a", "b")); // => -1
法則
Ordは次の3つのlawを満足する必要がある。
- 反射律: compare(a, a) <= 0
- 反対称律: compare(a, b) <= 0 かつ compare(b, a) <= 0 ならば a = b
- 推移律: compare(a, b) <= 0 かつ compare(b, c) <= 0 ならば compare(a, c) <= 0
combinators
contramap
Ord<A>とBをAに変換する関数B => AからOrd<B>を作成する。
console.log(Ord.contramap((s: string) => s.length)(N.Ord).compare("a", "b")); // => 0
reverse
結果として出てきたOrderingをひっくり返す。
console.log(Ord.reverse(N.Ord).compare(1, 2)); // => 1
tuple
複数のOrdインスタンスからタプルのOrdインスタンスを作成する。
const tuple3Ord = Ord.tuple(S.Ord, N.Ord);
console.log(tuple3Ord.compare(["a", 2], ["b", 1])); // => -1
console.log(tuple3Ord.compare(["a", 2], ["a", 1])); // => 1
constructors
fromCompare
比較関数からOrdインスタンスを作成する。
インスタンス作成のヘルパーファクトリー関数。
注意すべきは比較関数はOrdering(= -1|0|1)を返す必要がある。
const lenStrOrd = Ord.fromCompare<string>((a, b) => {
if (a.length === b.length) {
return 0;
} else if (a.length < b.length) {
return -1;
} else {
return 1;
}
});
console.log(lenStrOrd.compare("a", "b")); // => 0
console.log(lenStrOrd.compare("ab", "b")); // => 1
equalsDefault
Orderingを返すような比較関数をとって、二値が等しいかをbooleanで返す関数を返す。
まず二値が===で比較され等しくない場合にのみ渡された比較関数での等値性検証が行われる。
console.log(Ord.equalsDefault(lenStrOrd.compare)("a", "b")); // => true
console.log(Ord.equalsDefault(lenStrOrd.compare)("a", "a")); // => true
// イメージはこんな感じ
// first === second || compare(first, second) === 0;
instances
Contravariant
OrdのContravariantインスタンス。
console.log(
Ord.Contravariant.contramap(N.Ord, (s: string) => s.length).compare("a", "b")
); // => 0
getMonoid
Monoid<Ord<A>>を取得する。
Monoid.concat(ord1, ord2)はまずord1で比較し、等値な場合にのみord2で比較するようなOrdを返す。
Monoid.emptyは常に0を返すようなOrdである。
const numOrdMon = Ord.getMonoid<number>();
console.log(numOrdMon.empty.compare(1, 2)); // => 0
console.log(
numOrdMon
.concat(
N.Ord,
Ord.fromCompare<number>(() => 1)
)
.compare(0, 0)
); // => 0
getSemigroup
getMonoidのemptyがなくなったバージョン。
utils
between
特定区間に収まっているかを判定する。
console.log(Ord.between(N.Ord)(0, 10)(3)); // => true
console.log(Ord.between(N.Ord)(0, 10)(0)); // => true
clamp
値を特定区間に押し込める。
console.log(Ord.clamp(N.Ord)(0, 10)(3)); // => 3
console.log(Ord.clamp(N.Ord)(0, 10)(11)); // => 10
console.log(Ord.clamp(N.Ord)(0, 10)(-1)); // => 0
geq/gt/leq/lt
よくある大なり・小なり・以上・以下の比較関数。
console.log(Ord.geq(N.Ord)(2, 1)); // => true
console.log(Ord.geq(N.Ord)(1, 1)); // => true
console.log(Ord.gt(N.Ord)(2, 1)); // => true
console.log(Ord.gt(N.Ord)(1, 1)); // => false
console.log(Ord.leq(N.Ord)(1, 1)); // => true
console.log(Ord.leq(N.Ord)(1, 2)); // => true
console.log(Ord.lt(N.Ord)(1, 1)); // => false
console.log(Ord.lt(N.Ord)(1, 2)); // => true
max/min
大きい方、小さい方を取得する。
console.log(Ord.max(N.Ord)(1, 2)); // => 2
console.log(Ord.min(N.Ord)(1, 2)); // => 1
オブジェクトに対するOrdを作成する
Eq.structのようにオブジェクトに対して簡易的にOrdを作成する関数は用意されていない。
ここではMonoidを使ったオブジェクト用のOrd作成方法を紹介する。
まずは下のようなオブジェクトが存在するとする。
type Person = {
name: string;
age: number;
};
それぞれの要素で判断するOrdインスタンスを作成する。
その後OrdのMonoidで各要素のOrdを合成してすべての要素で比較するOrdを作成する。
const byNameOrd = Ord.contramap((p: Person) => p.name)(S.Ord);
const byAgeOrd = Ord.contramap((p: Person) => p.age)(N.Ord);
const personOrd = Ord.getMonoid<Person>().concat(byNameOrd, byAgeOrd);
このOrdを使えばPersonの要素すべてを気にしながらcompareできる。
console.log(
personOrd.compare({ name: "taro", age: 20 }, { name: "taro", age: 20 })
); // => 0
console.log(
personOrd.compare({ name: "taro", age: 10 }, { name: "taro", age: 20 })
); // => -1
console.log(
personOrd.compare({ name: "jon", age: 20 }, { name: "taro", age: 20 })
); // => -1
注意すべきはMonoidのconcatのロジックによって先にbyNameでの比較が行われるということだ。
つまり、もしnameが異なるならばageが何であろうと名前比較の結果が全体の結果になってしまう。
ageでの比較が行われるのはname同士が等しいときのみである。