はじめに
fp-ts はTypescriptの型システムを使って関数型プログラミングをしようという趣旨のライブラリです。
正確なところは公式ドキュメントのこちら に書いてあります。
学習リソースのこちらを動かしながら理解する過程を書いた記事になります。実装が古くなっている点は変更しています。
Semigroup とは
簡単にSemigroupについてです。群となる条件のうち結合法則を満たすものを半群(Semigroup)と呼びます。
半群(A, *)
は集合 A
と結合操作 *
のペアで表されます。
*
は2つの値をとる結合操作になります。型で表すと以下のような関数になります。
*: (x: A, y: A) => A
Semigroup を作る
結合操作を持っていれば Semigroup ということで fp-ts には
interface Semigroup<A> {
concat: (x: A, y: A) => A
}
と定義されてます。
はじめに Semigroup を返す関数を作ってみます。
import * as S from "fp-ts/Semigroup";
const getSemigroupMin = (): S.Semigroup<number> => {
return { concat: (x, y) => (x <= y ? x : y) };
};
const getArraySemigroup = <A = never>(): S.Semigroup<A[]> => {
return { concat: (x, y) => x.concat(y) };
}
ジェネリクスを使って何らかの配列を結合する Semigroup が作れました。
実は汎用的な Semigroup はインスタンスメソッドが用意されていて組み合わせるだけで生成ができたりします。
import * as S from "fp-ts/Semigroup";
import * as A from "fp-ts/Array";
const semigroupMin: S.Semigroup<number> = S.min(N.Ord);
const arraySemigroup: S.Semigroup<string[]> = A.getSemigroup<string>();
学習リソースでは非推奨のAPIの利用があったので書き換えています。
v2.0.0 から型ごとにexportする設計変更があったようです。それに合わせて命名がシンプルになっています。
続きを読み進めてベクターの実装をします
import * as S from "fp-ts/Semigroup";
import * as N from "fp-ts/number";
type Point = {
x: number;
y: number;
};
export const semigroupPoint = S.struct<Point>({
x: N.SemigroupSum,
y: N.SemigroupSum,
});
type Vector = {
from: Point;
to: Point;
};
export const semigroupVector = S.struct<Vector>({
from: semigroupPoint,
to: semigroupPoint,
});
新しいAPIの S.struct
書き換えています。Typescriptで簡潔にSemigroupが作れました。
抽象度が上がり応用できそうな気がしてきました。 次に座標を判定する関数を作ってみます。
import * as F from "fp-ts/function";
import * as B from "fp-ts/boolean";
import * as S from "fp-ts/Semigroup";
const semigroupPredicate: S.Semigroup<(p: Point) => boolean> = F.getSemigroup(
B.SemigroupAll
)<Point>();
B.SemigroupAll
は2つの入力が true
のとき true
を返します。
F.getSemigroup
は集合の型を関数に変換するヘルパーです。
型を展開すると以下のような Semigroup が生成されるイメージです。
type Predicate = (p Point) => boolean
type SemigroupPredicate = {
concat: (x: Predicate, y: Predicate) => Predicate;
}
動かしてみます。
const isPositiveX = (p: Point): boolean => p.x >= 0;
const isPositiveY = (p: Point): boolean => p.y >= 0;
const isPositiveXY = semigroupPredicate.concat(isPositiveX, isPositiveY);
console.log([
isPositiveXY({ x: 1, y: 1 }),
isPositiveXY({ x: 1, y: -1 }),
isPositiveXY({ x: -1, y: 1 }),
isPositiveXY({ x: -1, y: -1 }),
]);
// [ true, false, false, false ]
座標が第一象限に存在するか判定する関数が組み上がりました。
Folding
Semigroupのインターフェースでは2つの要素しか結合できませんでした。そこで複数の要素を畳み込むように結合してくれるのが Folding です。
import * as S from "fp-ts/Semigroup";
const min = S.concatAll(S.min(N.Ord))(0);
console.log([
min([-2, -1, 1, 2, 3]),
min([1, 2, 3]),
]);
// [ -2, 0 ]
v2.0.0 では大胆な変更が入っており fold
ではなく concatAll
になっていました。
S.min
では、単に2つの入力から小さい値を取るだけの関数でしたが S.concatAll
で配列から最小値を取得できるようになりました。
終わりに
fp-ts
の Semigroup について基本的な動作を確認してきました。
Semigroup を応用できるように引き続き利用していきたいと思います。