2015年2月2日
Q. (関数型)リアクティブプログラミングとは何ですか?
(2009-06-22)by Stack Overflow
本記事は、原著者の許諾のもとに翻訳・掲載しております。
Wikipediaで リアクティブプログラミング (reactive programming)を調べてみました。また、 関数型リアクティブプログラミング (functional reactive programming, FRP)についても少し説明があったので確認してみましたが、どちらも説明が大雑把です。
実際のところ関数型リアクティブプログラミングとはどういう意味なのでしょうか?リアクティブプログラミングは(非リアクティブプログラミングと比べて)何で構成されているのですか?私は命令型のオブジェクト指向言語を使っているので、そのパラダイムに関連させて説明して頂けると大変有難いです。
asked by JtR
Answer(s)
FRPの感触をまず試してみたいのであれば、 1998年に発表された古典のFran tutorialに目を通してみると良いでしょう。 これは動画で説明されています。 論文では、Functional Reactive Animationを読んでみて、 更に詳細を知りたければ私のホームページにある出版物のリンクをたどってみてください。 また、Haskell wikiに掲載されている FRPのリンクもチェックしてみてください。
個人的には、FRPがどのように実装されるのかということよりも、 FRPにはどういった意味があるのかを考えてみたいと思います。 ですから、Thomas Kが回答しているような表示や実装に関連する表現(グラフ、ノード、エッジ、ファイアリング、実行など)は 使わずにFRPを説明しようと思います。 なぜなら、多くの実装スタイルがあるものの、どれもFRPが何なのかは説明していないからです。
私は、Laurence Gのコメントと同じ意見です。 彼は、FRPを「“時間とともに変化する”値を表すデータ型」とシンプルに表現しています。 従来の命令型プログラミングは、状態や状態変化をとおして間接的にのみ、これらの動的な値を保存します。 すべての履歴(過去、現在、未来)には、ファーストクラスの表示はありません。 さらに、命令型パラダイムは一時的に離散されているので、離散的に変化する値のみが(間接的に)保存されます。 一方、FRPはこれらの変化する値を直接保存するので、連続して変化する値でも問題はありません。
FRPは、命令型プログラミングの並行処理でいつも問題となる、 理論的にも実用的にもぐちゃぐちゃな状況に巻き込まれることなく並行処理されるという点で異例でもあります。 意味的にはFRPの並行処理とは、きめ細かく、決定的で連続的であるということです (私がここで話しているのは、実装ではなくその意味です。実装する際は、並行処理もしくは並行を行わないこともあります)。 厳密にFRPを用いる理由を考える場合でも、ちょっとした理由でFRPを使用する場合でも、決定的であることがとても重要な意味を持っています。 並行処理は、(非決定的なインターリービングであるため)命令型プログラミングでは多大な複雑性を生みますが、 FRPを使うとそういった面倒がありません。
結局、FRPとは何でしょう? 以下のまとめを参考にご自身で考えてみてください。
- 動的であり変化する値(すなわち、“時間とともに変化する”値)は、それ自体でファーストクラスの値となっています。それらを定義し、組み合わせ、そして関数の入力・出力に渡すことができます。私はこれを“behavior”と呼んでいます。
- Behaviorは、連続する(静的な)behaviorや(時計のような)時間といったプリミティブなものから構成され、順番そして並列に接続されます。n個のbehaviorは、“点別”つまり連続して時間とともに変化する、(静的な値の)n変数の関数を適用することによって組み合わされます。
- 離散的な現象を構成するために、FRPには(有限または無限に)発生するストリームを持つ他の“event”の型(群)があります。それぞれの発生は、時間と値に結びついています。
- すべてのbehaviorやeventを組み入れることができる集合的なボキャブラリーを考え出すために、いくつかの事例を試してみます。より一般的でシンプルになるようにバラバラになるまで組み立てなおしてみましょう。
- しっかり理解できているか確認するために、表示的意味論の手法を使って、作り上げたモデル全体に合成性を構築してみましょう。 つまり(a)それぞれの型は、“意味”の付随するシンプルで正確な数学的な型を持ちます。 そして(b)プリミティブとオペレータもそれぞれ構成を表す関数として、シンプルで正確な意味を持ちます。 決して、絶対に、この意味付けの過程には実装に関する考えを持ち込まないようにしてください。 どういうことかさっぱり理解できないなら、 (a) Denotational design with type class morphismsと、 (b) Push-pull functional reactive programming (実装に関する項目は無視してください)の論文に目を通してみてください。 (c) Haskellでの表示的意味論に関するWikibooksのページ も確認しておきましょう。 表示的意味論は、Christopher StracheyとDana Schottの2人によって導入された手法です。 簡単で使いやすいStracheyによる手順と、(ソフトウェアをデザインするには)難しくてあまり使い勝手のよくないScottによる手順の2つで構成されている手法であることに気をつけてください。
以上の原則に従って考えてみれば、FRPの本質とは何か、多少は理解することができることでしょう。
私がどうやってこれらの原則にたどりつくことができたかというと、ソフトウェアをデザインするたびにいつも、 あなたと同じように「FRPとは何だろう?」と考えていたからです。 表示的意味論を使うことでようやく、この疑問を解決する正確なフレームワークを得ることができました。 表示的意味論が、私の美学にぴったり合っていたのです(操作的意味論や公理的意味論では満足できませんでした)。 表示的意味論を使って、改めてbehaviorとは何か考えてみたところ、すぐに次のように理解することができました。 すなわち、命令型で計算すると時間的に離散するというbehaviorの性質とは、 behaviorそのものを表すというよりむしろ、あるスタイルをもった機械に対する調和を意味する、ということです。 私は、“(連続した)時間を表す機能”という表現がもっともシンプルで正確にbehaviorを言い表していると思います。 これが私の考えるモデルです。このモデルなら、連続した決定的な並行処理でも美しく簡単に楽しく扱うことができるのです。
実際にこのモデルを正確に効率よく実装するのはかなりチャレンジングなのですが、それはまた別の話です。
Answerer : Conal
純粋なfunctional programmingを使った場合には、副作用が起こることは一切ありません。 とはいえ多くのソフトウェアのタイプ(例えば、インタラクティブなソフトウェアならどれでも)にとっては、 ある程度の副作用は必ず発生するものです。
機能的なスタイルは維持しつつ、behaviorのような副作用も利用する手法の1つがfunctional reactive programmingです。 つまりこれは、functional programmingとreactive programmingの組み合わせです。 (あなたがリンクを貼ったWikipediaの記事は、reactive programmingに関するものですね)。
Reactive programmingを理解するためにはまず、“時間とともに変化する”値を表す特定のデータ型が存在することを確認しましょう。 こういった時間とともに変化する値を計算するためには、計算処理自体が時間の経過に応じて変化する値でなければなりません。
例えば、マウスの座標が時間とともに変化する整数値のペアで表示されるように設定します。 以下のような事例を考えてみましょう(これは疑似コードです)。
x = <mouse-x>;
y = <mouse-y>;
この事例では、時間の流れの中のどの瞬間でも、xとyがマウスの座標を表します。 非reactive programmingとは異なり、reactive programmingではこうした割り当てが必要なのは一度だけで、 この後も常にxとyの変数は“最新”に自動更新されます。 これがreactive programmingがfunctional programmingと一緒にうまく機能できる理由です。 Reactive programmingを使うと、変数を変化させる必要はなくなります。 それでも、これまでは変数を使わないと得られなかった成果が、ほとんど同じように得られるのです。
この手法を使っていくつかの計算をしてみた場合、結果として得られる値も、時間の変化に応じた値になります。例えば次の通りです。
minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;
この事例では、minXは常にマウスがポイントするx座標より16小さい値になります。reactiveに関連するライブラリを使うと、次のようなこともできます。
rectangle(minX, minY, maxX, maxY)
この場合、マウスを囲うように32×32の正方形の枠が描かれ、ポインタがどこへ動こうと一緒についてくることになります。
このfunctional reactive programmingに関する論文 を読むと、より理解が深まるでしょう。
Answerer : Laurence Gonsalves
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa