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がガベージコレクションの対象となった際に、これらの依存関係が自動的にクリーンアップされるメモリセーフな設計が施されています。