LoginSignup
10
0

More than 1 year has passed since last update.

fp-tsを理解する:Semigroup

Last updated at Posted at 2021-12-04

はじめに

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 を応用できるように引き続き利用していきたいと思います。

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0