Chapter 1: すべての基礎 combineLatestWith
Unit 4で確立したApplicative Functorの概念、すなわち「独立したコンテナを2項演算で合成する」という操作を、Timeline
ライブラリで具現化するものがcombineLatestWith
です。これは、非同期で独立した複数のソースを合成するために設計された、Applicative操作の具体的な実装です。
後続の章で登場するanyOf
やsumOf
といった高レベルな合成関数は、すべてこのcombineLatestWith
というプリミティブな操作の上に成り立っています。
combineLatestWith
という名前は、他の主要なリアクティブプログラミングライブラリ(RxJSなど)でも標準的に使われている用語であり、その振る舞いを正確に表現しています。すなわち、複数のソースから最新の(latest) 値を組み合わせ(combine)、提供された関数を使って(with) 新しい値を生成する、という意味が込められています。
F#: combineLatestWith: ('a -> 'b -> 'c) -> Timeline<'a> -> Timeline<'b> -> Timeline<'c>
Section titled “F#: combineLatestWith: ('a -> 'b -> 'c) -> Timeline<'a> -> Timeline<'b> -> Timeline<'c>”TS: combineLatestWith<A, B, C>(f: (valA: A, valB: B) => C) => (timelineA: Timeline<A>) => (timelineB: Timeline<B>): Timeline<C>
Section titled “TS: combineLatestWith<A, B, C>(f: (valA: A, valB: B) => C) => (timelineA: Timeline<A>) => (timelineB: Timeline<B>): Timeline<C>”combineLatestWith
の型シグネチャは、Unit 4で学んだApplicative Functorのmap2
パターンに準拠しています。
このシグネチャは、以下の要素で構成されています。
('a -> 'b -> 'c)
: 2項演算関数。1つ目の引数として'a
型、2つ目の引数として'b
型の値を受け取り、'c
型の結果を返します。Timeline<'a>
: 1つ目のソースTimeline
。このTimeline
の最新値が、2項演算関数の第1引数に渡されます。Timeline<'b>
: 2つ目のソースTimeline
。このTimeline
の最新値が、2項演算関数の第2引数に渡されます。Timeline<'c>
: 結果として生成される新しいTimeline
。2項演算関数の実行結果が、このTimeline
の値となります。
combineLatestWith
の振る舞いは、初期化と更新の2つのフェーズで定義されます。
-
初期化: 結果
Timeline
が生成されると、combineLatestWith
は即座に両方のソースTimeline
の現在値を確認し、2項演算関数を適用して結果Timeline
の初期値を決定します。 -
更新: 結果
Timeline
は、両方のソースTimeline
を監視します。いずれかのソースが更新されるたびに、両方のソースから最新の現在値を取得し、再度2項演算関数を適用して、結果Timeline
に新しい値を定義します。
実践コード例
Section titled “実践コード例”2つの独立したカウンターの合計値をリアクティブに算出するTimeline
を作成してみましょう。
// TSconst counterA = Timeline(10);const counterB = Timeline(20);
// 2項演算(+)を使って、2つのTimelineを合成するconst sumTimeline = combineLatestWith((a: number, b: number) => a + b)(counterA)(counterB);
// 初期値は 10 + 20 = 30console.log(sumTimeline.at(Now)); // 30
// counterAを更新するconsole.log("Updating CounterA to 15...");counterA.define(Now, 15);
// sumTimelineは自動的に再計算される (15 + 20 = 35)console.log("Current Sum after A updated: %d", sumTimeline.at(Now)); // 35
この操作は内部で、2つのソースTimeline
から結果Timeline
への依存関係をDependencyCore
に登録します。また、timeline.ts
のFinalizationRegistry
(F#のGcRegistry
に相当)を利用することで、結果Timeline
がガベージコレクションの対象となった際に、これらの依存関係が自動的にクリーンアップされるメモリセーフな設計が施されています。