raimond-klavins-gmxGONiOPqY-unsplash.jpg

fp-tsとSemigroup

 
0
このエントリーをはてなブックマークに追加
Kazuki Moriyama
Kazuki Moriyama (森山 和樹)

TypeScriptの関数型ライブラリのfp-tsには型クラスとしてSemigroupがいる。
その紹介。

https://gcanti.github.io/fp-ts/modules/Semigroup.ts.html

Semigroupとは

定義的には以下のような感じ。(実際は違うけど)

interface Semigroup {
  readonly concat: (x: A, y: A) => A
}

足し算的な操作を司る型クラスだと思えばいい。
Semigroupが満たすべきlawとして結合法則がある。

concat(x, concat(y, z)) = concat(concat(x, y), z)

どこから計算しても同じ。

Semigroupの使い方はこんな感じ。

import * as S from "fp-ts/string";
import * as N from "fp-ts/number";

console.log(S.Semigroup.concat("a", "b")); // => ab
console.log(N.SemigroupSum.concat(2, 3)); // => 5
console.log(N.SemigroupProduct.concat(2, 3)); // => 6

stringやnumberに対してはインスタンスがすでに用意されている。
とくにnumberは足し算でも掛け算でもSemigroupが構成できるのでそのどちらも用意されている。

コンビネータ

作成されたSemigroupにはいろんなコンビネータが定義されている。

intercalate

元のSemigroupを変換して要素をconcatする際に、間になにかを挟んでくれるようにする。

console.log(intercalate(" + ")(S.Semigroup).concat("a", "b")); // => a + b

reverse

元のSemigroupを変換して入力要素を入れ替えて処理を行ってくれる。

console.log(reverse(S.Semigroup).concat("a", "b")); // => ba

struct

複数のSemigroupを組み合わせてオブジェクトに対するSemigroupを構成してくれる。
オブジェクトが全面的に活躍するTypeScriptでは非常に重宝する。

type Person = {
  name: string;
  age: number;
};

const personSemigroup = struct<Person>({
  name: S.Semigroup,
  age: N.SemigroupSum,
});

console.log(
  personSemigroup.concat({ name: "taro", age: 20 }, { name: "tanaka", age: 30 })
); // => { name: 'tarotanaka', age: 50 }

tuple

タプル(長さの決まった配列)に対してのSemigroupを要素ごとのSemigroupから作成できる。

console.log(tuple(S.Semigroup, S.Semigroup).concat(["a", "b"], ["c", "d"])); // => [ 'ac', 'bd' ]

TypeScriptは結構すごいので長さが異なるとちゃんとコンパイルで弾いてくれる。

console.log(tuple(S.Semigroup, S.Semigroup).concat(["a", "b"], ["c"]));
// error TS2345: Argument of type '[string]' is not assignable to parameter of type 'readonly [string, string]'.
// Property '1' is missing in type '[string]' but required in type 'readonly [string, string]'.

constructors

いろんな方法でSemigroupを作成する。

constant

どんなものを入力しても定数を返すSemigroupを作成する。
変な感じがするけど、これも立派にSemigroupとして成立している。

console.log(constant("a").concat("b", "c")); // => a

max/min

2つの要素のうち大きな/小さな方を返すSemigroupを作成する。
ただし順番を司る型クラスOrdを受け取る必要がある。

console.log(max(N.Ord).concat(1, 2)); // => 2
console.log(min(N.Ord).concat(1, 2)); // => 1

順序比較からもSemigroupを構成できるのが面白い。

インスタンス

fp-tsにはいろんな型に対して使えるインスタンスが前もって定義されている。
以下で定義されているものだけではなく、各データ型のmoduleにもインスタンスは用意されている。

first

常に最初の要素を返すインスタンス。

console.log(first<number>().concat(1, 2)); // => 1

last

常に2つ目の要素を返すインスタンス。

console.log(last<number>().concat(1, 2)); // => 2

semigroupVoid

void用のインスタンス。

型クラスの定義

export interface Semigroup<A> extends Magma<A> {}

Magmaという型クラスを継承している。
concat自体はMagmaに定義されていて、Magmaとの違いは結合法則のlawがあるかどうか。
ただ型定義的にlawを表現できないので見た目上はSemigroupとMagmaは変わらない。

便利関数

concatAll

Semigroupを元にして配列の中身をすべて足し合わせてくれる関数。
ただし空配列の場合に対応するために初期値を設定してあげなければならない。

console.log(concatAll(S.Semigroup)("")(["a", "b", "c"])); // => abc
info-outline

お知らせ

K.DEVは株式会社KDOTにより運営されています。記事の内容や会社でのITに関わる一般的なご相談に専門の社員がお答えしております。ぜひお気軽にご連絡ください。