概要

この記事では、 Reactive Programming を理解するための技術について述べます。 背景として、プログラムの表現方法や環境とのやりとりについて概観し、次に Reactive Programming について述べます。 なおこの記事では、個別の Reactive の実装を用いてアプリケーションを実装することは扱いませんので注意してください。

1. はじめに

プログラムをその表現方法から見た場合、主に2つに分類されます。
  1. 文字列として表現
  2. グラフなど、視覚的に表現
文字列としてプログラムを記述する方法は、 C 言語などのプログラミング言語を用いる方法です。 グラフなど、視覚的にプログラムを記述する方法は、 LabVIEW などのソフトウェアを使用した方法があります。 作成するプログラムや用途に応じて、これらの記述方法は使い分けられます。 プログラムを、外部から見た場合には次のように表現できます。
何らかの入力を受け取り、受け取った入力を用いて、ある特定の処理を行い、何らかのデータを出力する。
次の図は、これを模式的に表したものです。

プログラムの模式図

プログラムは、入力に対する前提条件や出力に対する前提条件は異なっても、このような構造で表現されます。 プログラムの実行に対し、入力の与えられるタイミングは、以下になります。
  1. プログラムの実行前に、全ての入力が与えられる。
  2. プログラムの実行サイクル中の任意のタイミングで、全ての入力が与えられる。
  3. プログラムの実行サイクル中の任意のタイミングで、ある一つの入力が与えられる。
入力-1とその他の条件との違いは、入力がプログラムの実行前に全て与えられ、プログラムの実行中に入力は変化しないことです。 入力-1で実行されるプログラムの例は、単純なコンパイラや画像の変換プログラムです。 コンパイラは、与えられたソースコードをある特定の処理を行い、プログラムまたはアセンブリコードを出力します。 入力-2では、プログラムでの処理は全ての入力で与えられてから処理が開始されるか、またはある決まったタイミングで入力を処理するというどちらかの形式になります。 入力-3では、入力が与えられたタイミングで、プログラムの処理が行われます。 プログラムの出力は、入力に対する条件より以下のようになります。
  1. プログラムの実行後
  2. プログラムの実行サイクル中の任意のタイミング
入力-1で実行されるプログラムでは、出力も同様にプログラムの実行が完了したタイミングで出力が得られます。 入力-2、または入力-3で実行されるプログラムでは、入力の条件で規定されるタイミングで出力が得られます。 従って、プログラムの実行サイクル中のある決まったタイミングで入力を与える場合には、同時に直前に与えた入力を処理した結果となる出力が得られます。 プログラムの実行サイクル中の任意のタイミングで入力が与えられる場合には、入力を与えたタイミングから処理が完了次第出力が得られます。 この記事では、これらの観点より Reactive Programming を理解するための基礎的な知識を概説します。 本稿の構成については、まず2節にてプログラムの表現方法について述べます。 次に3節で、プログラムと環境間でのやりとりについて述べます。 4節では、 Reactive program について述べるという形になっています。

2. プログラムの表現方法

本節では、プログラムの表現について述べていきます。 プログラムとは、コンピューターで実行する処理を記述したものであり、コンピューターで実行される処理はそのままでは人間には理解が難しいものです。 そのため、プログラマーはコンピューターで実行する処理を抽象化した人工言語を用いてプログラムを記述します。 人工言語は、ほとんどの場合文字列で表現可能な言語として定義さます。 また、プログラムをグラフとして表現することもあります。

テキストベースの表現

C 言語に代表されるように、ほとんどの場合プログラムは文字列で表現可能な人工言語を用いて記述されます。
int some_function(int x, int a, int b) {
    return a * x * x + b * x + 5;
}
文字列で表現可能な人工言語を用いることにより、プログラムが実行されるコンピューターの詳細をプログラマーに隠蔽することができます。 また、人間が理解しやすい表現でプログラムを記述できます。

グラフベースの表現

プログラムは、その処理の実行の仕方からグラフとして表現することができます。 ビジュアルプログラミング言語では、プログラムをグラフとして記述します。

ビジュアルプログラミング言語の例

ビジュアルプログラミング言語では、プログラムで使用するデータの流れを中心に記述を行うものや、プログラムの実行の流れを中心に記述を行うものなどがあります。 ビジュアルプログラミング言語を用いることで、コンピューターの詳細を隠蔽したままプログラムの記述が行えます。

Dataflow プログラミング

Dataflow プログラミングとは、データの流れを中心にプログラムを記述する方法です。 Dataflow プログラミングでは、プログラムは有向グラフとして記述されます。

Dataflow programming でのプログラムの例

Dataflow プログラミングでは、プログラムは処理と処理間を流れるデータを使って表現されます。 また、処理をノードとして記述しデータをノード間のエッジとして表現することもあります。 通常、処理はブラックボックスとして表現され、入力が与えられれば即座に処理が開始されます。 そのため、 Dataflow プログラミングは、並列に動作するプログラムの記述を行うのに向いています。 Dataflow プログラミングは、データの処理の流れを表現します。 そのため、実行の制御の流れを表現するために、マージとスイッチという処理を用います。 マージは、複数の入力を受け取り単一の出力を行うもので、その入力は複数の入力データと一つの制御用データになります。 制御用データを受け取ると、適切な入力データを自身の出力とします。 スイッチは、一つの入力データと制御用データを受け取り複数の出力を行います。 制御用データを受け取ると、入力を適切な出力エッジに伝搬させます。 Dataflow プログラミングでは、処理はブラックボックスとして表現します。 これにより、演算の粒度の細かいシステムから、大規模なシステムまで記述することができます。

3. プログラムと環境間でのやりとり

プログラムとは、大きく分類すると以下の3種類になります。[1]
  1. Transformational program
  2. Interactive program
  3. Reactive program
Transformational program とは、与えられた入力に対し特定の処理を行い、結果を出力するプログラムです。 Interactive program とは、プログラム自身で定めた特定の間隔で入力を受け取るプログラムになります。 Reactive program とは、 Interactive program と同様に環境とのやりとりを行いますが、入力を受け取る間隔は環境によって決定されるプログラムです。 Reactive program では、環境からの要求があった時、その応答として何らかの処理を行います。 前述の分類より、環境とプログラムの関係において、 Transformational program と Interactive program は、プログラムによって入力を受け取るタイミングを決定しています。 Transformational program では、プログラムの実行開始よりも前に全ての入力が決まっている必要があります。 また Interactive program では、プログラムにより決定された時間間隔よりも前に全ての入力が決まっていなければいけません。 それに対し、 Reactive program では環境と連続的にやりとりを行います。 複雑なアプリケーションやシステムでは、これらのプログラムが協調動作し構成されています。 例えば、アプリケーションのメニューやスクロールバーは、ショートカットキーやマウスによる操作に応答し、メニューの表示や画面のスクロールを行います。 メニューから何らかの操作を選択した場合、アプリケーションを実行する OS (Interactive program)などに処理の開始を要求します。 また、コンパイラーや数値演算などの、 Transformational program の実行を開始を要求します。

4. Reactive Programming

Reactive Programming とは、 Reactive program を実装するためのプログラミング・パラダイムです。 したがって、以下のような特徴があります 。
  • 入力に対する応答として、何らかの処理を行う
  • 処理の結果を、後続の処理に伝搬させる
  • これらの処理は、非同期実行である
このような特徴から Reactive Programming は、 User Interface や(ほぼ)リアルタイムで行われるアニメーションなどを実現するのに適しています。 Reactive programming では、プログラムを グラフ としても表現でき、そのグラフのノードは処理を、エッジはノードの依存関係(データの流れ)を表します。 したがって、 Reactive Programming は Dataflow programming の一種とみなせます。 Reactive programming では、入力データの受け取り以外にもユーザー操作などのイベントが伝搬したことで、処理を開始することが出来ます。

4.1 Propagation of Updated Data/Event

プログラムをグラフとして表現した場合、ノードが入力データまたはイベントにより処理を開始する方法は、大きく2種類あります。 それらは、以下の通りです。
  • Push 型
  • Pull 型
Push 型では、ノードは処理の開始に必要なデータ/イベントを受け取ると即座に処理を開始します。 ノードでの処理完了後、ノードは処理結果のデータ/イベントを出力エッジに渡し、後続のノードへ伝搬させます。 Pull 型では、データ/イベントを必要とするノードが、それらの出力を行うノードに対して当該のデータ/イベントを要求します。 要求したデータ/イベントがあればノードは処理を開始します。 要求したデータ/イベントがまだ準備されていなければ、ノードは準備されるまで処理の開始を待ちます。

4.2 Cyclic Dependencies

プログラムを表したグラフが有向非巡回グラフであれば、各ノードの依存関係はトポロジカルソートした結果と一致します。 しかしながら、一般的にプログラムでは前回の処理結果を用いて処理を行うことが多く、ループのような閉路を含む場合にプログラムの動作が停止する問題があります。 この問題を解決するために、閉路に対し遅延のような処理を導入します。 遅延処理により現在の処理を停止させず、次回の処理を行う時に現在の処理結果を伝搬させます。

5. まとめ

プログラムの表現形式や環境とのデータのやり取りについて述べ、 Reactive Programming の概念を説明しました。 Reactive Programming は、多くのプログラマーが慣れ親しんだプログラミングの記述方法とは異なります。 そのため、この記事で述べたような、 Dataflow Programming に関する知識や、データの伝搬方法についての知識が重要となるでしょう。

参考文献

  1. G é rard Berry, “Real Time Programming: Special Purpose Or General Purpose Languages,” apports de recherche, 1989.