Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Welcome to the "Fantasy Land" 🧚 − 代数的構造をめぐる冒険 −

Welcome to the "Fantasy Land" 🧚 − 代数的構造をめぐる冒険 −

このスライドは、2025/11/23「TSKaigi Hokuriku 2025」で発表したものです。

cf. https://hokuriku.tskaigi.org/talks/31

Avatar for TAKASE Kazuyuki

TAKASE Kazuyuki

November 23, 2025

More Decks by TAKASE Kazuyuki

Other Decks in Programming

Transcript

  1. 余談) 運営スタッフの @berlysia さんとは、同じ会社 & 本部の所属です   自己紹介 - 高瀬

    和之 (TAKASE Kazuyuki) >=> 𝕏:@Guvalif Profile { works = [ "IoT エンジニア", "フロントエンド・エンジニア", "開発人事", "TechPM (← 現職)", "エンジニア育成 (← 複業)" ], memo = "関数型 XXX を名乗りがち 😗" }
  2. 冒険の地図 (本編) 🧭 >=> `Promise` の型定義 `Result` の型定義 `Promise` の振る舞い

    `Result` の振る舞い 代数的構造の仕様 抽象化した型定義 抽象化した振る舞い 代数的構造  "Fantasy Land" 🧚
  3. `Promise` の型定義 es5.d.ts を参照することで `PromiseLike<T>` という型定義が得られる (冗長さを省くと以下): ``` ``` >=>

    interface PromiseLike<T> { then<A, B>( onfulfilled: (value: T ) => A | PromiseLike<A>, onrejected: (reason: any) => B | PromiseLike<B>, ): PromiseLike<A | B>; } - `then` メソッドを持つ - `onfulfilled` および `onrejected` として、Callback を設定できる - いずれの Callback も ある型の値 もしくは `PromiseLike` で包まれたある型の値 を返す
  4. `Result` の型定義 (1) result.ts を参照することで `Result<T, E>` という型定義が得られる (冗長さを省くと以下): ```

    ``` >=> type Result<T, E> = Ok<T, E> | Err<T, E>; class Ok<T, E> { map<A> (f: (t: T) => A ): Result<A, E> { ... } andThen<A, F>(f: (t: T) => Result<A, F>): Result<A, E | F> { ... } } class Err<T, E> { map<B> (f: (t: T) => B ): Result<B, E> { ... } andThen<B, F>(f: (t: T) => Result<B, F>): Result<B, E | F> { ... } }
  5. `Result` の型定義 (2) 型定義が対称なので、簡単のために `Ok<T, E>` のみに着目すると ... ``` ```

    >=> type Result<T, E> = Ok<T, E> | Err<T, E>; class Ok<T, E> { map<A> (f: (t: T) => A ): Result<A, E> { ... } andThen<A, F>(f: (t: T) => Result<A, F>): Result<A, E | F> { ... } } - `map` および `andThen` メソッドを持つ - それぞれ `f` として Callback を設定できる: - `map` の Callback は ある型の値 を返す - `andThen` の Callback は `Result<?, F>` で包まれたある型の値 を返す
  6. e.g. `onfulfilled` に着目してみる (1) `onfulfilled` に関係ない部分を削除し Pretify すると ... ```

    ``` >=> interface PromiseLike<T> { then<A, B>( onfulfilled: (value: T ) => A | PromiseLike<A>, onrejected : (reason: any) => B | PromiseLike<B>, ): PromiseLike<A | B>; } interface PromiseLike<T> { then<A>(onfulfilled: (value: T) => A | PromiseLike<A>): PromiseLike<A>; }
  7. ここで、シンボル名を無視して 型に着目 すると、定義の類似 が見て取れる ``` ``` e.g. `onfulfilled` に着目してみる (2)

    `then` の定義を Overload すると ... (※ 厳密には完全分離できない可能性があることに注意) ``` ``` >=> interface PromiseLike<T> { then<A>(onfulfilled: (value: T) => A ): PromiseLike<A>; then<A>(onfulfilled: (value: T) => PromiseLike<A>): PromiseLike<A>; } class Ok<T, E> { map<A> (f: (t: T) => A ): Result<A, E> { ... } andThen<A, F>(f: (t: T) => Result<A, F>): Result<A, E | F> { ... } }
  8. 現在地点の確認 >=> `Promise` の型定義 `Result` の型定義 `Promise` の振る舞い `Result` の振る舞い

    代数的構造の仕様 抽象化した型定義 抽象化した振る舞い 代数的構造  "Fantasy Land" 🧚 ✅ ✅ ✅
  9. 暗黙的な `this` にも型を明示することで、`then` メソッドの振る舞いを整理してみる: with Promise _then(onfulfilled)_ `Promise` の振る舞い >=>

    Promise<T> _onfulfilled_ Promise<A> this T A _then(onfulfilled)_ Promise<T> Promise<A> this T _onfulfilled_ ※ 簡単のために `onfulfilled` のみに着目 then<A>(this: PromiseLike<T>, onfulfilled: (value: T) => A ): PromiseLike<A> then<A>(this: PromiseLike<T>, onfulfilled: (value: T) => PromiseLike<A>): PromiseLike<A>
  10. 暗黙的な `this` にも型を明示することで、`map` および `andThen` メソッドの振る舞いを整理してみる: with Result _map(f)_ `Result`

    の振る舞い >=> Result<T, E> _f_ Result<A, E> this T A _andThen(f)_ Result<T, E> Result<A, E> this T _f_ ※ 簡単のために型パラメータ `E = F` として図示 map<A> (this: Result<T, E>, f: (t: T) => A ): Result<A, E> andThen<A>(this: Result<T, E>, f: (t: T) => Result<A, E>): Result<A, E>
  11. `PromiseLike<?>` と `Result<?, E>` を、ざっくり `M<?>` として抽象化してみる: _foo(f)_ 似ている Interface

    に着目する >=> M<T> _f_ M<A> this T A _bar(kf)_ M<T> M<A> this T _kf_ ※ いったんそれぞれのメソッド名は仮置き with M foo<A>(this: M<T>, f: (t: T) => A ): M<A>; bar<A>(this: M<T>, kf: (t: T) => M<A>): M<A>;
  12. 現在地点の確認 >=> `Promise` の型定義 `Result` の型定義 `Promise` の振る舞い `Result` の振る舞い

    代数的構造の仕様 抽象化した型定義 抽象化した振る舞い 代数的構造  "Fantasy Land" 🧚 🤔 ✅ ✅
  13. - 型 の表現は似ている ✅ - Interface の表現は似ている ✅ - しかし、振る舞いの対応

    までは確信が持てない 🤔 現状の整理 >=> _foo(f)_ M<T> _f_ M<A> this T A _bar(kf)_ M<T> M<A> this T _kf_ with M
  14. "代数的構造" を考える 【 "代数的構造" とは?】 - おもに抽象代数学で議論される概念 - 型 (≒

    集合) と Interface (≒ 演算) に関して、どのようなルールが満たされるか を定めるもの - プログラミングで言うところの、デザインパターンに相当するもの 【 なぜ "代数的構造" を考えたいか?】 - 数学に由来する概念なので、論証の正確性 を担保しやすい 󰢏 - 19 世紀から研究されているので、巨人の肩に乗りやすい 🥳 >=>
  15. 事前知識:TypeScript ⇔ Fantasy Land における型シグネチャの比較 >=> 関数型 多相型 (いわゆるジェネリクス) カインド

    (型関数) 型制約 // `,` 区切りで引数を増やす (_0: A, _1: B) => C -- `->` で連結して引数を増やす A -> B -> C // 項に紐づく `<T>` 部分で多相性を明示 length: <T>(_0: T[]) => number -- 小文字で記述することで多相性を明示 length :: [t] -> Int // 型に紐づく `<T>` 部分で型引数を明示 type Thenable<T> = ... -- スペース区切りで型引数を明示 Thenable t // `extends` に続けて制約を記述 sort: <T extends Ord>(_0: T[]): => T[] -- `=>` の手前に制約を記述 sort :: Ord t => [t] -> [t]
  16. 次のような型 (と Interface) を考える: ``` ``` e.g. Setoid の仕様を読み解いてみる >=>

    interface S { ['fantasy-land/equals'](s: S): boolean; } `S` が Setoid という代数的構造を満たすとしたら 、任意のインスタンス `a`, `b`, `c` に対して: 1. `a['fantasy-land/equals'](a) === true` 2. `a['fantasy-land/equals'](b) === b['fantasy-land/equals'](a)` 3. If `a['fantasy-land/equals'](b)` and `b['fantasy-land/equals'](c)` , then `a['fantasy-land/equals'](c)` ... これら 3 つのルールが成立 する
  17. 試しに以下の Interface に関して、Fantasy Land の仕様を探してみる 🔍: というわけで ... >=> _bar(kf)_

    M<T> M<A> this T _kf_ with M ⇔ ※ もう片方の Interface に関しては、ぜひ皆さまのお手元で探索まで 💪
  18. 次のような型 (と Interface) を考える: ``` ``` 候補:Chain の仕様 >=> interface

    M<T> { ['fantasy-land/chain'](kf: (t: T) => M<A>): M<A>; } `M<T>` が Chain という代数的構造を満たすとしたら、任意のインスタンス `m` に対して: - `m['fantasy-land/chain'](f)['fantasy-land/chain'](g)` is equivalent to `m['fantasy-land/chain'](x => f(x)['fantasy-land/chain'](g))` ... このルールが成立する ⇒ `PromiseLike<T>` と `Result<T, E>` の 双方でルールが成立する なら、Chain として抽象化 できる!
  19. 現実案:Property-based Testing を用いる 宣言的な記述 にもとづいて、ランダムな入力パターンを自動生成しテストを行う 仕組みのこと TypeScript においては fast-check などのライブラリで実現できる

    📝 【 代数的構造の現実的な確認手順 】 1. 型や Interface を観察し、共通化できそうかあたりを付ける 2. Fantasy Land を参照し、仕様の候補を洗い出す 3. Propety-based Testing を活用し、仕様を (大まかに) 満たせるか確認する >=>
  20. Property-based Testing の実装例 (1) `Promise` そのままだと Interface を満たせないので、ラップした class を準備:

    >=> class M<T> { private constructor(public readonly value: PromiseLike<T>) {} ['fantasy-land/chain']<A>(kf: (t: T) => M<A>): M<A> { return new M(this.value.then(t => kf(t).value)); } static of<T>(t: T): M<T> { return new M(Promise.resolve(t)); } }
  21. Property-based Testing の実装例 (2) >=> Chain の仕様をもとにテストを作成 ( `Result` 版も同様な流れで作成できる

    ): const chainAssociativity = fc.asyncProperty( fc.integer(), async (x) => { const m = M.of(x); // f と g を動的に用意するとより精緻 const lhs = await m['fantasy-land/chain'](f)['fantasy-land/chain'](g).value; const rhs = await m['fantasy-land/chain'](x => f(x)['fantasy-land/chain'](g)).value; return lhs === rhs; }, ); await fc.assert(chainAssociativity, { numRuns: 100 });
  22. 実際どうなるか? 以下の条件のもと、`Promise` と `Result` は Chain として抽象化しても問題ない ✅: - `onrejected`

    が実行されることのない `Promise` のみを用いる - 型定義が `(reason: any) => B | PromiseLike<B>` な時点で怪しさはあったが、実際ダメ 😇 - `Promise<Promise<T>>` については考えない - なぜならネストした `Promise` のインスタンスは そもそも作ることができない から 代数的構造に着目したおかげで、破綻の兆候を事前に検知 できたとも言える → より良い Interface の探索 にもつながる ( e.g. `() => Promise<Result<T, E>>` ) >=>
  23. 現在地点の確認 >=> `Promise` の型定義 `Result` の型定義 `Promise` の振る舞い `Result` の振る舞い

    代数的構造の仕様 抽象化した型定義 抽象化した振る舞い 代数的構造  "Fantasy Land" 🧚 ✅ ✅ 𝑭𝒊𝒏.
  24. Fantasy Land の気になるところ class の利用を前提にしている こと 🤔 (※ class を絶対悪とは思っていません)

    【 class の良いところ 】 - 🥳 戻り値を単一の class に閉じることで、メソッドチェーンによる見通しの良さが得られる - 🥳 class 自体が 1 つの名前空間として機能する 【 class の微妙なところ 】 - 🤔 Serialize と Deserialize に関する考慮が必要 - 🤔 不変クラスを徹底しないと、内部に持つ状態が不用意な複雑性を招きかねない >=>
  25. e.g. 関数による実装 Currying Data-last Function を用いることで、関数合成がメソッドチェーン とみなせるが ... >=> const

    data = [ { name: 'john', age: 20, gender: 'm' }, { name: 'marry', age: 22, gender: 'f' }, { name: 'samara', age: 24, gender: 'f' }, ]; R.pipe( data, R.filter((x) => x.gender === 'f'), R.groupBy((x) => x.age), ); data .filter((x) => x.gender === 'f') .groupBy((x) => x.age); - そのためだけにライブラリを導入するか?🤔 - 初見だと少し ギョッ としないか?🤔
  26. - `Promise` と `Result` を例に、型や Interface を通じて相互の類似を考えた - 一方で、似ている からといって

    必ずしも共通化できるとは限らない ことも確かめた - Fantasy Land (代数的構造 の仕様) は、そのような 共通化を紐解くヒント 【 さらに学ぶために 】 代数的構造は "データ構造" とも密接な関連を持つので、以下の記事も参考まで 📝 まとめ >=>