コンピューティングにおいて、リアクティブプログラミングは、データストリームと変更の伝播に関係する宣言型 プログラミングパラダイムです。このパラダイムを使用すると、静的 (配列など) または動的 (イベントエミッターなど) データストリームを簡単に表現できるほか、関連する実行モデル内に推定された依存関係が存在することを伝えて、変更されたデータフローの自動伝播を容易にすることができます。[要出典]
たとえば、命令型プログラミング設定では、式が評価された瞬間にの結果が に割り当てられ、その後、との値がの値に影響を与えずに変更されることa := b + cを意味します。一方、リアクティブプログラミングでは、またはの値が変わるたびにの値は自動的に更新され、プログラムがの値を再割り当てするためにステートメントを明示的に再記述する必要はありません。[要出典]ab + cbcaabca := b + ca
var b = 1 var c = 2 var a = b + c b = 10 console . log ( a ) // 3 ("=" はリアクティブ代入演算子ではないため、12 ではありません)
// ここで、明示的に初期化されたときだけでなく、参照された変数(演算子の右側)が変更されたときにも変数の値を変更する(演算子の右側のコードを実行し、結果を左側の変数に割り当てる)特別な演算子 "
$ =" があるとします。var b = 1 var c = 2 var a $ = b + c b = 10 console . log ( a ) // 12
もう 1 つの例は、 Verilogなどのハードウェア記述言語です。リアクティブ プログラミングを使用すると、変更が回路全体に伝播するときにその変更をモデル化できます。[引用が必要]
リアクティブプログラミングは、インタラクティブなユーザーインターフェイスとほぼリアルタイムのシステムアニメーションの作成を簡素化する方法として提案されています。[引用が必要]
たとえば、モデル・ビュー・コントローラ(MVC)アーキテクチャでは、リアクティブプログラミングにより、基礎となるモデルの変更が関連するビューに自動的に反映されるようになります。[1]
リアクティブプログラミング言語を作成するためのアプローチ
リアクティブ プログラミング言語の作成には、いくつかの一般的なアプローチが採用されています。 1 つのアプローチは、さまざまなドメイン制約に固有の専用言語の仕様指定です。このような制約は通常、リアルタイム、組み込みコンピューティング、またはハードウェア記述によって特徴付けられます。別のアプローチでは、リアクティブのサポートを含む汎用言語の仕様指定が含まれます。その他のアプローチは、プログラミング言語と一緒に、またはプログラミング言語上でリアクティブを可能にするプログラミングライブラリ、つまり組み込みドメイン固有言語の定義と使用で表現されます。これらのさまざまなアプローチの仕様指定と使用により、言語機能のトレードオフが発生します。一般に、言語の制限が厳しいほど、関連するコンパイラと分析ツールが開発者に情報を提供できる可能性が高くなります (たとえば、プログラムが実際にリアルタイムで実行できるかどうかを分析する場合など)。特異性における機能的なトレードオフにより、言語の一般的な適用性が低下する可能性があります。
プログラミングモデルとセマンティクス
リアクティブ プログラミングは、さまざまなモデルとセマンティクスによって制御されます。これらは、次の次元に沿って大まかに分類できます。
- 同期: 時間の同期モデルと非同期モデル
- 決定論: 決定論的評価プロセスと結果と非決定論的評価プロセスと結果
- 更新プロセス:コールバックとデータフローとアクター
実装技術と課題
実装の本質
リアクティブ プログラミング言語のランタイムは、関係するリアクティブ値間の依存関係を識別するグラフで表されます。このようなグラフでは、ノードは計算行為を表し、エッジは依存関係をモデル化します。このようなランタイムは、関係する入力値が変更されると、新たに実行する必要があるさまざまな計算を追跡するために、前述のグラフを使用します。
変更伝播アルゴリズム
データ伝播の最も一般的なアプローチは次のとおりです。
- プル: 値のコンシューマーは実際にはプロアクティブであり、監視対象ソースに対して定期的に値を照会し、関連する値が利用可能になると反応します。イベントや値の変更を定期的にチェックするこの方法は、一般にポーリングと呼ばれます。
- プッシュ: 値が利用可能になると、値のコンシューマーはソースから値を受け取ります。これらの値は自己完結型です。つまり、必要な情報がすべて含まれており、コンシューマーがさらに情報を照会する必要はありません。
- プッシュプル: 値の消費者は変更通知を受け取ります。これは変更の簡単な説明で、たとえば「何らかの値が変更されました」などです。これがプッシュ部分です。ただし、通知には必要な情報がすべて含まれているわけではありません (つまり、実際の値は含まれません)。そのため、消費者は通知を受け取った後、ソースに詳細情報 (特定の値) を問い合わせる必要があります。これがプル部分です。この方法は、消費者が関心を持つ可能性のある大量のデータがある場合によく使用されます。したがって、スループットとレイテンシを削減するために、軽量の通知のみが送信され、その後、詳細情報を必要とする消費者がその特定の情報を要求します。このアプローチには、通知が送信された後、ソースが追加情報の多数の要求に圧倒される可能性があるという欠点もあります。
何をプッシュしますか?
実装レベルでは、イベント反応はグラフの情報の伝播で構成され、変更の存在を特徴付けます。その結果、そのような変更の影響を受ける計算は古くなり、再実行のためにフラグを立てる必要があります。このような計算は通常、関連するソースでの変更の推移的閉包(つまり、ソースが影響を与える推移的依存関係の完全なセット) によって特徴付けられます。変更の伝播により、グラフのシンクの値が更新される場合があります。
グラフ伝播情報は、ノードの完全な状態、つまり関連するノードの計算結果で構成できます。このような場合、ノードの前の出力は無視されます。別の方法としては、デルタ伝播、つまり増分変更伝播があります。この場合、情報はグラフのエッジに沿って増殖し、前のノードがどのように変更されたかを示すデルタのみで構成されます。このアプローチは、ノードが大量の状態データを保持している場合に特に重要です。そうでなければ、最初から再計算するにはコストがかかります。
デルタ伝播は本質的には増分コンピューティング の分野を通じて広範に研究されてきた最適化であり、そのアプローチにはビュー更新問題を含む実行時の満足度が必要です。この問題は、変化するデータ ビューの維持を担当する データベースエンティティの使用によって特徴付けられるという悪名高い特徴があります。
もう 1 つの一般的な最適化は、単項変更蓄積とバッチ伝播の採用です。このようなソリューションは、関係するノード間の通信が減るため、高速化できます。次に、含まれる変更の性質を推論し、それに応じて変更を加える最適化戦略を採用できます。たとえば、バッチ内の 2 つの変更は互いにキャンセルされるため、単に無視されます。さらに別の利用可能なアプローチは、無効通知伝播と呼ばれます。このアプローチでは、無効な入力を持つノードが更新をプルし、その結果、ノード自体の出力が更新されます。
依存関係グラフの構築には主に 2 つの方法が使用されます。
- 依存関係のグラフは、イベント ループ内で暗黙的に維持されます。明示的なコールバックを登録すると、暗黙的な依存関係が作成されます。したがって、コールバックによって誘発される制御の反転はそのまま残ります。ただし、コールバックを機能的にする (つまり、ユニット値ではなく状態値を返す) には、そのようなコールバックを合成する必要があります。
- 依存関係のグラフはプログラム固有であり、プログラマによって生成されます。これにより、コールバックの制御反転のアドレス指定が2 つの方法で容易になります。グラフが明示的に指定されるか (通常は埋め込み可能なドメイン固有言語(DSL)を使用)、またはグラフが、効果的なアーキタイプ言語を使用した表現と生成によって暗黙的に定義されます。
リアクティブプログラミングにおける実装の課題
不具合
変更を伝播する場合、式の値がソース プログラムの自然な結果にならないように伝播順序を選択することができます。これは例で簡単に説明できます。seconds現在の時刻 (秒単位) を表すために毎秒変化するリアクティブ値があるとします。次の式を考えてみましょう。
t = 秒 + 1 g = (t > 秒)

tは常に より大きいためseconds、この式は常に true 値に評価されるはずです。残念ながら、これは評価の順序に依存する可能性があります。 がseconds変化すると、 と条件の 2 つの式を更新する必要がありますseconds + 1。最初の式が 2 番目の式より前に評価される場合、この不変条件は保持されます。ただし、 の古い値tと の新しい値を使用して条件が最初に更新される場合seconds、式は false 値に評価されます。これはグリッチと呼ばれます。
一部のリアクティブ言語は不具合がなく、この特性を証明しています[要出典]。これは通常、式を位相的にソートし、位相的な順序で値を更新することで実現されます。ただし、これは値の配信が遅れるなど、パフォーマンスに影響する可能性があります (伝播の順序による)。したがって、場合によってはリアクティブ言語で不具合が許容されるため、開発者は値が一時的にプログラム ソースに対応しなくなる可能性や、一部の式が複数回評価される可能性 (たとえば、t > secondsは の新しい値がseconds到着したときに 1 回、t更新時にもう 1 回、合計 2 回評価される可能性があります) に注意する必要があります。
循環依存関係
依存関係のトポロジカル ソートは、依存関係グラフが有向非巡回グラフ(DAG) であることに依存します。実際には、プログラムがサイクルを持つ依存関係グラフを定義する場合があります。通常、リアクティブ プログラミング言語では、リアクティブ更新を終了できるように、何らかの要素を「バック エッジ」に沿って配置することで、このようなサイクルを「破壊」することが想定されています。通常、言語では、delayこの目的のために更新メカニズムで使用される のような演算子が提供されます。 は、delay後続のものは「次のタイム ステップ」で評価される必要がある (現在の評価を終了できる) ことを意味するためです。
可変状態との相互作用
リアクティブ言語では通常、その表現が純粋に関数的であると想定されます。これにより、更新メカニズムは更新を実行するさまざまな順序を選択し、特定の順序を指定しないままにすることができます (これにより最適化が可能になります)。ただし、リアクティブ言語が状態を持つプログラミング言語に埋め込まれている場合、プログラマーは可変操作を実行できる可能性があります。この相互作用をスムーズにする方法は、未解決の問題のままです。
場合によっては、原則的な部分的な解決策が可能です。そのような解決策として次の 2 つが挙げられます。
- 言語は「可変セル」という概念を提供する場合があります。可変セルとは、リアクティブ更新システムが認識しているセルであり、セルに加えられた変更はリアクティブ プログラムの残りの部分に伝播します。これにより、プログラムの非リアクティブ部分は従来の変更を実行できると同時に、リアクティブ コードがこの更新を認識して応答できるため、プログラム内の値間の関係の一貫性が維持されます。このようなセルを提供するリアクティブ言語の例としては、FrTime があります。[2]
- 適切にカプセル化されたオブジェクト指向ライブラリは、カプセル化された状態の概念を提供します。したがって、原理的には、そのようなライブラリは言語のリアクティブ部分とスムーズにやり取りすることができます。たとえば、オブジェクト指向ライブラリのゲッターにコールバックをインストールして、リアクティブ更新エンジンに状態の変化を通知したり、リアクティブコンポーネントの変更をゲッターを通じてオブジェクト指向ライブラリにプッシュしたりできます。FrTime はこのような戦略を採用しています。[3]
依存関係のグラフの動的更新
一部のリアクティブ言語では、依存関係のグラフは静的、つまりプログラムの実行中はグラフが固定されています。他の言語では、グラフは動的、つまりプログラムの実行中に変化することがあります。簡単な例として、次の例を考えてみましょう ( はsecondsリアクティブ値です)。
t =
if ((秒数 mod 2) == 0):
秒 + 1
それ以外:
秒 - 1
終わり
t + 1
毎秒、この式の値は、依存する別の反応式に変わりますt + 1。したがって、依存関係のグラフは毎秒更新されます。
依存関係の動的な更新を許可すると、表現力が大幅に向上します (たとえば、動的な依存関係はグラフィカル ユーザー インターフェイス(GUI) プログラムで日常的に発生します)。ただし、リアクティブ更新エンジンは、毎回式を再構築するか、式のノードを構築したまま非アクティブにしておくかを決定する必要があります。後者の場合、アクティブになるはずのないノードが計算に参加しないようにする必要があります。
コンセプト
明示性の度合い
リアクティブ プログラミング言語は、データ フローが矢印を使用して設定される非常に明示的なものから、データ フローが命令型プログラミングや関数型プログラミングに似た言語構造から派生する暗黙的なものまで多岐にわたります。たとえば、暗黙的に持ち上げられた関数型リアクティブ プログラミング(FRP) では、関数呼び出しによって暗黙的にデータ フロー グラフのノードが構築されることがあります。動的言語用のリアクティブ プログラミング ライブラリ (Lisp の「Cells」ライブラリや Python の「Trellis」ライブラリなど) は、関数の実行中に読み取られた値の実行時分析から依存関係グラフを構築できるため、データ フロー仕様を暗黙的かつ動的にすることができます。
リアクティブ プログラミングという用語は、データ フロー グラフ内の個々のノードが相互に通信する通常のプログラムであるソフトウェア エンジニアリングのアーキテクチャ レベルを指す場合もあります。
静的か動的か
リアクティブ プログラミングは、データ フローが静的に設定される完全に静的なプログラミングにすることも、プログラムの実行中にデータ フローが変更できる動的なプログラミングにすることもできます。
データ フロー グラフでデータ スイッチを使用すると、ある程度、静的なデータ フロー グラフが動的に見え、その区別が若干あいまいになります。ただし、真の動的リアクティブ プログラミングでは、命令型プログラミングを使用してデータ フロー グラフを再構築できます。
高階リアクティブプログラミング
リアクティブ プログラミングは、データ フローを使用して他のデータ フローを構築できるという考えをサポートしている場合、高次のプログラミングであると言えます。つまり、データ フローから得られる値は、最初のデータ フローと同じ評価モデルを使用して実行される別のデータ フロー グラフです。
データフローの差別化
理想的には、すべてのデータ変更が即座に伝播されますが、実際にはこれを保証することはできません。代わりに、データフローグラフのさまざまな部分に異なる評価優先順位を与える必要がある場合があります。これは、差別化されたリアクティブプログラミングと呼ばれます。[引用が必要]
たとえば、ワードプロセッサでは、スペルエラーのマーク付けは文字の挿入と完全に同期している必要はありません。ここで、差別化されたリアクティブプログラミングを使用して、スペルチェッカーの優先度を低くし、他のデータフローを瞬時に維持しながらスペルチェッカーを遅らせることができる可能性があります。
ただし、このような差別化により、設計の複雑さが増します。たとえば、異なるデータ フロー領域を定義する方法や、異なるデータ フロー領域間でのイベントの受け渡しを処理する方法を決定する必要があります。
リアクティブプログラミングの評価モデル
リアクティブ プログラムの評価は、必ずしもスタック ベースのプログラミング言語の評価方法に基づいているわけではありません。代わりに、一部のデータが変更されると、変更されたデータから部分的または完全に派生したすべてのデータに変更が伝播されます。この変更の伝播はさまざまな方法で実現できますが、おそらく最も自然な方法は無効化/遅延再検証スキームです。
データ構造が特定の形状である場合、更新の複雑さが指数関数的に増加する可能性があるため、スタックを使用して単純に変更を伝播するだけでは問題が発生する可能性があります。そのような形状の 1 つは「繰り返しダイヤモンド形状」として説明でき、次の構造を持ちます: A n →B n →A n+1、 A n →C n →A n+1 、ここで n=1,2... 一部のデータがまだ無効化されていない場合にのみ無効化を伝播し、後で必要に応じて遅延評価を使用してデータを再検証することで、この問題を克服できます。
リアクティブプログラミングの本質的な問題の 1 つは、通常のプログラミング言語では評価されて忘れられる計算のほとんどを、データ構造としてメモリ内に表現する必要があることです。[要出典]これにより、リアクティブプログラミングはメモリを大量に消費する可能性があります。ただし、 loweringと呼ばれる研究によって、この問題を克服できる可能性があります。[4]
一方、リアクティブプログラミングは、「明示的な並列処理」[引用が必要]とも言える形式であり、並列ハードウェアのパワーを活用するのに役立ちます。
観察者パターンとの類似点
リアクティブ プログラミングは、オブジェクト指向プログラミングでよく使用されるオブザーバー パターンと基本的に類似しています。ただし、データ フローの概念をプログラミング言語に統合すると、表現が容易になり、データ フロー グラフの粒度を高めることができます。たとえば、オブザーバー パターンは一般にオブジェクト/クラス全体のデータ フローを記述しますが、オブジェクト指向のリアクティブ プログラミングはオブジェクト/クラスのメンバーを対象にすることができます。
アプローチ
命令形
リアクティブプログラミングを通常の命令型プログラミングと融合することは可能です。このようなパラダイムでは、命令型プログラムはリアクティブデータ構造上で動作します。[5]このような設定は命令型制約プログラミングに似ていますが、命令型制約プログラミングは双方向のデータフロー制約を管理するのに対し、命令型リアクティブプログラミングは一方向のデータフロー制約を管理します。1つの参照実装は、JavaScriptへのQuantumランタイム拡張の提案です。
オブジェクト指向
オブジェクト指向リアクティブ プログラミング (OORP) は、オブジェクト指向プログラミングとリアクティブ プログラミングを組み合わせたものです。おそらく、このような組み合わせを作成する最も自然な方法は、メソッドとフィールドの代わりに、オブジェクトが依存する他のリアクションが変更されたときに自動的に再評価するリアクションを持つことです。 [引用が必要]
OORP 言語が命令型のメソッドを維持する場合、命令型リアクティブ プログラミングのカテゴリにも分類されます。
機能的
関数型リアクティブ プログラミング (FRP) は、関数型プログラミングにおけるリアクティブ プログラミングのプログラミング パラダイムです。
俳優ベース
アクターはリアクティブシステムを設計するために提案されており、分散リアクティブシステムを開発するために、関数型リアクティブプログラミング(FRP)やリアクティブストリームと組み合わせて使用されることが多い。 [6] [7] [8] [9]
ルールベース
比較的新しいカテゴリーのプログラミング言語では、制約(ルール)を主なプログラミング概念として使用します。これは、すべての制約を満たすイベントへの反応で構成されます。これにより、イベントベースの反応が容易になるだけでなく、リアクティブプログラムがソフトウェアの正確性に役立ちます。ルールベースのリアクティブプログラミング言語の例としては、関係代数に基づいたAmpersandがあります。[10]
実装
- ReactiveXは、RxJs、RxJava、Rx.NET、RxPy、RxSwift などの複数の言語実装を備えたストリーム、オブザーバブル、演算子を使用してリアクティブ プログラミングを実装するための API です。
- Elm (プログラミング言語) Web ユーザー インターフェイスのリアクティブ構成。
- Reactive Streams は、非ブロッキングバックプレッシャーによる非同期ストリーム処理の JVM 標準です。
- ObservableComputations は、クロスプラットフォームの .NET 実装です。
- Svelte は、 JavaScript のように見えながら、JavaScript が通常そうではないところで自然に反応する、バリアントJavaScript 構文の形で反応性をもたらします。
- Solid.js は、反応性の高いJSXテンプレートとともに、JavaScript 構文のセマンティクスを変更することなくJavaScriptに反応性をもたらします。
- Quantum JS は、JavaScript のランタイム拡張機能であり、命令型リアクティブ プログラミングを言語に導入し、リアクティブ スペクトルにまったく新しいカテゴリを作成します。
参照
- Observable (コンピューティング)、リアクティブプログラミングで観測可能。
参考文献
- ^ Trellis、モデル・ビュー・コントローラとオブザーバーパターン、Tele コミュニティ、2016 年 3 月 3 日にオリジナルからアーカイブ、 2009 年 7 月 27 日に取得。
- ^ 「Call-by-Value 言語に動的データフローを埋め込む」cs.brown.edu。2016 年 10 月 18 日にオリジナルからアーカイブ。2016 年 10 月 9 日に取得。
- ^ 「Crossing State Lines: Adapting Object-Oriented Frameworks to Functional Reactive Languages」cs.brown.edu。2016 年 10 月 9 日時点のオリジナルよりアーカイブ。2016年 10 月 9 日閲覧。
- ^ Burchett, Kimberley; Cooper, Gregory H; Krishnamurthi, Shriram、「Lowering: 透過的な機能的反応性のための静的最適化手法」、2007 ACM SIGPLAN シンポジウム「部分評価とセマンティクスベースのプログラム操作」の議事録(PDF)、71~80 ページ、2016 年 4 月 16 日にオリジナルからアーカイブ(PDF) 、 2014 年9 月 8 日に取得。
- ^ Demetrescu, Camil; Finocchi, Irene; Ribichini, Andrea (2011 年 10 月 22 日)、「データフロー制約によるリアクティブ命令型プログラミング」、2011 ACM 国際会議オブジェクト指向プログラミング システム言語およびアプリケーションに関する議事録、Oopsla '11、pp. 407–26、arXiv : 1104.2293、doi :10.1145/2048066.2048100、ISBN 9781450309400、S2CID 7285961。
- ^ Van den Vonder, Sam; Renaux, Thierry; Oeyen, Bjarno; De Koster, Joeri; De Meuter, Wolfgang (2020)、「リアクティブプログラミングのための厄介なチームへの取り組み:アクターリアクターモデル」、Leibniz International Proceedings in Informatics (LIPIcs)、vol. 166、pp. 19:1–19:29、doi : 10.4230/LIPIcs.ECOOP.2020.19、ISBN 9783959771542、S2CID 260445152、2021年10月20日にオリジナルからアーカイブ、2021年6月24日に取得。
- ^ Van den Vonder, Sam; Renaux, Thierry; De Meuter, Wolfgang (2022)、「分散リアクティブプログラムにおけるトポロジレベルのリアクティブ性:Flocksを使用したリアクティブ知人管理」、The Art, Science, and Engineering of Programming、第6巻、pp. 14:1–14:36、arXiv : 2202.09228、doi : 10.22152/programming-journal.org/2022/6/14、2023-03-15にオリジナルからアーカイブ、2023-03-16に取得
- ^ 柴内 一弘、渡辺 拓夫 (2018)、「アクターベースランタイムにおける分散機能リアクティブプログラミング」、第 8 回 ACM SIGPLAN 国際ワークショップ「アクター、エージェント、分散制御に基づくプログラミング」の議事録、Agere 2018、pp. 13–22、doi :10.1145/3281366.3281370、ISBN 9781450360661、S2CID 53113447、2021-06-24にオリジナルからアーカイブ、2021-06-24に取得
- ^ ロステンブルグ、レイモンド;ロブ・バッカー。ロブ・ウィリアムズ (2016)。 「13:ストリーミング」。アクション中のアッカ。米国コネチカット州グリニッジ: Manning Publications Co. p. 281.ISBN 978-1-61729-101-2。
- ^ Joosten, Stef (2018)、「Ampersand コンパイラを使用したプログラミング言語としての関係代数」、Journal of Logical and Algebraic Methods in Programming、vol. 100、pp. 113–29、doi : 10.1016/j.jlamp.2018.04.002、S2CID 52932824。
外部リンク
- リアクティブ プログラミングに関する調査 E. Bainomugisha、A. Lombide Carreton、T. Van Cutsem、S. Mostinckx、W. De Meuter による 2013 年の論文では、既存のリアクティブ プログラミング アプローチを調査し、分類しています。
- INRIA の MIMOSA プロジェクト - ENSMP、リアクティブ プログラミングに関する総合サイト。
- Observer パターンの非推奨 Ingo Maier、Tiark Rompf、 Martin Oderskyによる 2010 年の論文。Scalaプログラミング言語のリアクティブ プログラミング フレームワークの概要を説明しています。
- Scala.React での Observer パターンの廃止 Ingo による 2012 年の論文
- RxJS は、「観測可能なシーケンスを使用して非同期 [...] プログラムを作成する」ための Reactive Extensions ライブラリです。
- リアクティブ プログラミングの厄介な問題への取り組み: アクター リアクター モデル 命令型コードとリアクティブ コードを組み合わせたときに発生する問題を回避するために、「アクター」と「リアクター」のモデルを提案する 2020 年の論文。
