Chapter 3: foldTimelines — Monoidによる畳み込み
Chapter 2では、Monoidを形成する二項演算を用いて2つのTimelineを結合する方法を学びました。この章では、その概念をさらに拡張し、Timelineのリスト(任意の数のTimeline)を1つのTimelineに畳み込む方法を探求します。
そのための汎用的なツールがfoldTimelinesです。これは、関数型プログラミングにおける標準的な畳み込み操作であるfold(またはreduce)をTimelineの世界に応用したものであり、Chapter 2で確立したMonoidの構造に完全に依存しています。
foldTimelines API
Section titled “foldTimelines API”F#: foldTimelines: (Timeline<'b> -> Timeline<'a> -> Timeline<'b>) -> Timeline<'b> -> list<Timeline<'a>> -> Timeline<'b>
Section titled “F#: foldTimelines: (Timeline<'b> -> Timeline<'a> -> Timeline<'b>) -> Timeline<'b> -> list<Timeline<'a>> -> Timeline<'b>”TS: foldTimelines<A, B>(timelines: readonly Timeline<A>[], initialTimeline: Timeline<B>, accumulator: (acc: Timeline<B>, current: Timeline<A>) => Timeline<B>): Timeline<B>
Section titled “TS: foldTimelines<A, B>(timelines: readonly Timeline<A>[], initialTimeline: Timeline<B>, accumulator: (acc: Timeline<B>, current: Timeline<A>) => Timeline<B>): Timeline<B>”// TS 実装export const foldTimelines = <A, B>( timelines: readonly Timeline<A>[], initialTimeline: Timeline<B>, accumulator: (acc: Timeline<B>, current: Timeline<A>) => Timeline<B>): Timeline<B> => timelines.reduce(accumulator, initialTimeline);foldTimelinesはTimelineのリストを受け取り、Monoidの「単位元」を初期値として、リストの要素にMonoidの「二項演算」を次々と適用することで、単一のTimelineを生成します。
引数の内訳は、Monoidの構成要素と完全に対応しています。
timelines: 畳み込まれるTimelineのリスト。initialTimeline: Monoidの単位元(畳み込みの初期値)。accumulator: Monoidの二項演算(例:Chapter2で定義したorOf)。
高レベルAPIの導出
Section titled “高レベルAPIの導出”foldTimelinesの真価は、anyOfやsumOfのような直感的な高レベルAPIが、特別な実装を必要とせず、様々なMonoidとfoldTimelinesを組み合わせるだけで実現できる点にあります。それらはfoldTimelinesの薄いラッパーに過ぎません。
F#: anyOf: list<Timeline<bool>> -> Timeline<bool>
Section titled “F#: anyOf: list<Timeline<bool>> -> Timeline<bool>”TS: anyOf(booleanTimelines: readonly Timeline<boolean>[]): Timeline<boolean>
Section titled “TS: anyOf(booleanTimelines: readonly Timeline<boolean>[]): Timeline<boolean>”// TS 実装export const anyOf = (booleanTimelines: readonly Timeline<boolean>[]): Timeline<boolean> => { return foldTimelines(booleanTimelines, FalseTimeline, orOf);};複数のブール値タイムラインにまたがる論理和(OR)です。これは論理和Monoidを用いてfoldTimelinesを適用することで導出されます。
- 単位元:
Timeline(false) - 二項演算:
orOf
// TS 利用例const flags = [Timeline(true), Timeline(false), Timeline(false)];const hasAnyTrue = anyOf(flags);console.log(hasAnyTrue.at(Now)); // trueF#: allOf: list<Timeline<bool>> -> Timeline<bool>
Section titled “F#: allOf: list<Timeline<bool>> -> Timeline<bool>”TS: allOf(booleanTimelines: readonly Timeline<boolean>[]): Timeline<boolean>
Section titled “TS: allOf(booleanTimelines: readonly Timeline<boolean>[]): Timeline<boolean>”// TS 実装export const allOf = (booleanTimelines: readonly Timeline<boolean>[]): Timeline<boolean> => { return foldTimelines(booleanTimelines, TrueTimeline, andOf);};複数のブール値タイムラインにまたがる論理積(AND)です。これは論理積Monoidを用いて導出されます。
- 単位元:
Timeline(true) - 二項演算:
andOf
// TS 利用例const flags = [Timeline(true), Timeline(true), Timeline(false)];const allTrue = allOf(flags);console.log(allTrue.at(Now)); // falseTS: sumOf(numberTimelines: readonly Timeline<number>[]): Timeline<number>
Section titled “TS: sumOf(numberTimelines: readonly Timeline<number>[]): Timeline<number>”// TS 実装export const sumOf = (numberTimelines: readonly Timeline<number>[]): Timeline<number> => { return foldTimelines(numberTimelines, Timeline(0), addOf);};複数の数値タイムラインの合計です。これは加算Monoidを用いて導出されます。
- 単位元:
Timeline(0) - 二項演算:
addOf
// TS 利用例const numbers = [Timeline(10), Timeline(20), Timeline(30)];const total = sumOf(numbers);console.log(total.at(Now)); // 60TS: maxOf(timelines): Timeline<number>
Section titled “TS: maxOf(timelines): Timeline<number>”// TS 実装export const maxOf = (numberTimelines: readonly Timeline<number>[]): Timeline<number> => { return foldTimelines(numberTimelines, Timeline(-Infinity), maxOf2);};複数の数値タイムラインにまたがる最大値です。これは最大値Monoidから導出されます。
- 単位元:
Timeline(-Infinity) - 二項演算: タイムライン用の二項
max関数。
// TS 利用例const numbers = [Timeline(10), Timeline(50), Timeline(30)];const maximum = maxOf(numbers);console.log(maximum.at(Now)); // 50TS: minOf(timelines): Timeline<number>
Section titled “TS: minOf(timelines): Timeline<number>”// TS 実装export const minOf = (numberTimelines: readonly Timeline<number>[]): Timeline<number> => { return foldTimelines(numberTimelines, Timeline(Infinity), minOf2);};複数の数値タイムラインにまたがる最小値です。これは最小値Monoidから導出されます。
- 単位元:
Timeline(Infinity) - 二項演算: タイムライン用の二項
min関数。
// TS 利用例const numbers = [Timeline(10), Timeline(50), Timeline(30)];const minimum = minOf(numbers);console.log(minimum.at(Now)); // 10averageOf
Section titled “averageOf”TS: averageOf(timelines): Timeline<number>
Section titled “TS: averageOf(timelines): Timeline<number>”// TS 実装export const averageOf = (numberTimelines: readonly Timeline<number>[]): Timeline<number> => { if (numberTimelines.length === 0) return Timeline(0); return sumOf(numberTimelines).map(sum => sum / numberTimelines.length);};複数の数値タイムラインの平均です。これは単純なMonoidではありませんが、概念的には似ています。sumOfとタイムラインの数を探すために畳み込み、それらを割る新しいタイムラインを作成することで導出できます。
// TS 利用例const numbers = [Timeline(10), Timeline(20), Timeline(30)];const avg = averageOf(numbers);console.log(avg.at(Now)); // 20listOf
Section titled “listOf”F#: listOf: list<Timeline<'a>> -> Timeline<'a list>
Section titled “F#: listOf: list<Timeline<'a>> -> Timeline<'a list>”TS: listOf<A>(timelines: readonly Timeline<A>[]): Timeline<A[]>
Section titled “TS: listOf<A>(timelines: readonly Timeline<A>[]): Timeline<A[]>”// TS 実装export const listOf = <A>( timelines: readonly Timeline<A>[]): Timeline<A[]> => { const emptyArrayTimeline = Timeline<A[]>([]); return foldTimelines(timelines, emptyArrayTimeline, concatOf) as Timeline<A[]>;};複数のタイムラインを、それらの値の配列を保持する単一のタイムラインに結合します。これはChapter2で強調された配列Monoidから導出されます。
- 単位元:
Timeline([]) - 二項演算:
concatOf
この関数は複数のTimelineを、それらの値の配列を持つ単一のTimelineに単純に結合できるため、AIがコードを生成する際に扱いやすく、優先的に使用される傾向があります。
// TS 利用例const items = [Timeline("a"), Timeline("b"), Timeline("c")];const list = listOf(items);console.log(list.at(Now)); // ["a", "b", "c"]anyOfやsumOfを含む高レベルAPI群が、場当たり的で個別な関数の集まりではないことが明らかになりました。それらはすべて、単一の汎用的な抽象概念であるfoldTimelinesと、各型が持つMonoid構造から、数学的な必然性をもって導出されています。
combineLatestWith(Applicative) → 二項演算(Monoid) → foldTimelines → 高レベルAPIというこの階層構造こそが、このライブラリの設計美と、コードの重複を排除した堅牢性の核となっています。