コンテンツにスキップ

Chapter 1: すべての基礎 combineLatestWith

Unit 4で確立したApplicative Functorの概念、すなわち「独立したコンテナを2項演算で合成する」という操作を、Timelineライブラリで具現化するものがcombineLatestWithです。これは、非同期で独立した複数のソースを合成するために設計された、Applicative操作の具体的な実装です。

後続の章で登場するanyOfsumOfといった高レベルな合成関数は、すべてこの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パターンに準拠しています。

このシグネチャは、以下の要素で構成されています。

  1. ('a -> 'b -> 'c): 2項演算関数。1つ目の引数として'a型、2つ目の引数として'b型の値を受け取り、'c型の結果を返します。
  2. Timeline<'a>: 1つ目のソースTimeline。このTimelineの最新値が、2項演算関数の第1引数に渡されます。
  3. Timeline<'b>: 2つ目のソースTimeline。このTimelineの最新値が、2項演算関数の第2引数に渡されます。
  4. Timeline<'c>: 結果として生成される新しいTimeline。2項演算関数の実行結果が、このTimelineの値となります。

combineLatestWithの振る舞いは、初期化更新の2つのフェーズで定義されます。

  1. 初期化: 結果Timelineが生成されると、combineLatestWithは即座に両方のソースTimelineの現在値を確認し、2項演算関数を適用して結果Timelineの初期値を決定します。

  2. 更新: 結果Timelineは、両方のソースTimelineを監視します。いずれかのソースが更新されるたびに、両方のソースから最新の現在値を取得し、再度2項演算関数を適用して、結果Timelineに新しい値を定義します。

2つの独立したカウンターの合計値をリアクティブに算出するTimelineを作成してみましょう。

// TS
const counterA = Timeline(10);
const counterB = Timeline(20);
// 2項演算(+)を使って、2つのTimelineを合成する
const sumTimeline = combineLatestWith((a: number, b: number) => a + b)(counterA)(counterB);
// 初期値は 10 + 20 = 30
console.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.tsFinalizationRegistry(F#のGcRegistryに相当)を利用することで、結果Timelineがガベージコレクションの対象となった際に、これらの依存関係が自動的にクリーンアップされるメモリセーフな設計が施されています。