(関数型)反応型プログラミングとは何ですか?


1148

リアクティブプログラミングに関するウィキペディアの記事を読みました。関数型リアクティブプログラミングに関する小さな記事も読んだ。説明は非常に抽象的なものです。

  1. 関数型反応プログラミング(FRP)は実際にはどういう意味ですか?
  2. (非リアクティブプログラミングとは対照的に)リアクティブプログラミングは何で構成されていますか?

私の背景は命令型/オブジェクト指向言語なので、このパラダイムに関連する説明をいただければ幸いです。


159
アクティブな想像力と優れたストーリーテリングスキルを備えた男がここにいます。paulstovell.com/reactive-programming
melaos

39
誰かが本当にここで私たちのすべての自動編集のために「ダミーのための機能的反応プログラミング」を書く必要があります。私が見つけたすべてのリソースは、Elmでさえ、過去5年間にCSで修士号を取得したと想定しているようです。FRPに精通している人々は、素朴な視点から問題を見る能力を完全に失ってしまったように見えます。
TechZen 2014年

26
もう1つの優れたFRPイントロ:同僚のAndré が見逃していたReactive Programmingの紹介
Jonik

5
私が見た中で最高の1つ、例に基づく:gist.github.com/staltz/868e7e9bc2a7b8c1f754
Razmig

2
スプレッドシートの類推は、最初の大まかな印象として非常に役立ちます(ボブの回答:stackoverflow.com/a/1033066/1593924を参照)。スプレッドシートのセルは、他のセルの変更(プル)に反応しますが、他のセルには到達せず、他のセルを変更しません(プッシュしません)。最終結果として、1つのセルを変更し、他の何十億ものセルが「独立して」独自の表示を更新できます。
Jon Coombs、2015年

回答:


931

FRPの感触を知りたい場合は、1998年の古いFranチュートリアルから始めることができます。論文については、Functional Reactive Animationから始めて、私のホームページの出版物リンクのリンクとHaskell wikiのFRPリンクをフォローアップしてください。

個人的には、FRPがどのように実装されるかを説明する前に、FRPの意味について考えたいと思います。(仕様のないコードは質問のない答えであり、したがって「間違っていません」。)したがって、Thomas Kが別の答え(グラフ、ノード、エッジ、発射、実行、等)。多くの可能な実装スタイルがありますが、FRP 何であるかを示す実装はありません。

Laurence GのFRPは「「時間をかけて」値を表すデータ型」についてであるという簡単な説明に共鳴します。従来の命令型プログラミングは、これらの動的な値を、状態と変異を介して間接的にのみ取得します。完全な履歴(過去、現在、未来)には、ファーストクラスの表現はありません。さらに、命令パラダイムは時間的に離散的であるため、離散的に進化する値のみを(間接的に)キャプチャできます。対照的に、FRPはこれらの進化する値を直接キャプチャし、継続的に進化する値に問題はありません。

FRPはまた、命令型同時実行を悩ませる理論的および実用的なラットの巣を壊すことなく同時に実行されるという点でも珍しいです。意味的には、FRPの並行性は、きめ細かく確定的で、継続的です。(私は実装ではなく、意味について話しています。実装には、同時実行性または並列処理が含まれる場合と含まれない場合があります。)セマンティックの決定性は、厳密で非公式な推論にとって非常に重要です。並行性は、命令型プログラミングに非常に複雑さを追加しますが(非決定的インターリーブにより)、FRPでは簡単です。

では、FRPとは何でしょうか。あなた自身がそれを発明したかもしれません。これらのアイデアから始めます。

  • 動的/進化する値(つまり、「経時的」な値)は、それ自体がファーストクラスの値です。それらを定義して結合し、関数に渡したり、関数から渡したりすることができます。私はこれらを「行動」と呼んだ。

  • 動作は、定数(静的)動作や時間(クロックなど)などのいくつかのプリミティブから構築され、その後、順次および並列の組み合わせで構築されます。 n個の動作は、「静的な値に対して」n項関数を「ポイントごと」に、つまり継続的に適用することによって結合されます。

  • 離散現象を説明するには、別のタイプ(ファミリ)の「イベント」を用意します。各イベントには、発生のストリーム(有限または無限)があります。各発生には、関連付けられた時間と値があります。

  • すべての動作とイベントを構築できる構成的な語彙を考え出すには、いくつかの例を試してください。より一般的/単純な部分に分解し続けます。

  • しっかりとした地面にいることを確認するには、表示セマンティクスの手法を使用して、モデル全体に​​構成上の基盤を与えます。これは、(a)各タイプに対応する単純で正確な数学的タイプの「意味」があり、( b)各プリミティブと演算子は、構成要素の意味の関数として単純で正確な意味を持っています。 決して、実装の考慮事項を探索プロセスに混在させないでください。この説明が意味不明な場合は、(a)タイプクラスモーフィズムを使用したDenotational設計、(b)プッシュプル関数型反応プログラミング(実装ビットを無視)、および(c)Denotational Semantics Haskellウィキブックスのページを参照してください。。2つの創設者であるChristopher StracheyとDana Scottから、意味的な意味論には2つの部分があることに注意してください。

これらの原則に固執すれば、FRPの精神で多少なりとも何かが得られると思います。

これらの原則はどこで入手できましたか?ソフトウェア設計では、私はいつも同じ質問をします:「それはどういう意味ですか?」。拡散論的意味論はこの質問の正確なフレームワークを私に与えてくれました、そしてそれは私の美学に合うものです(どちらも私に不満を残す操作的または公理的意味論とは異なります)。だから私は行動とは何かを自問しましたか?命令型計算の時間的に離散した性質は、動作自体の自然な記述ではなく、特定のスタイルのマシンへの適応であることにすぐに気付きました。私が考えることができる最も単純で正確な振る舞いの記述は、単に「(連続的な)時間の関数」なので、それが私のモデルです。嬉しいことに、このモデルは、継続的かつ確定的な並行性を簡単かつ優雅に処理します。

このモデルを正しく効率的に実装することは非常に困難でしたが、それはまた別の話です。


78
私は関数型反応型プログラミングを知っています。それは私自身の研究(インタラクティブな統計グラフィックス)に関連しているようで、多くのアイデアが私の仕事に役立つと確信しています。しかし、言語を通り抜けるのは非常に難しいと思います。何が起こっているのかを理解するために、実際に「陳述セマンティクス」と「型クラス射」について学ばなければなりませんか?トピックへの一般的な聴衆の紹介は非常に役に立ちます。
ハドリー2009年

212
@Conal:あなたはあなたが話していることをはっきりと知っていますが、あなたの言語は私が計算数学の博士号を持っていると推測していますが、私はそうではありません。私はシステムエンジニアリングの経験があり、20年以上のコンピューターとプログラミング言語の経験がありますが、それでもあなたの返答には困惑します。英語で返信を再投稿してください;-)
mindplay.dk

50
@ minplay.dk:あなたの発言は、あなたが特に理解していないことについて続けるために私に多くを与えません、そして私はあなたが探している英語の特定のサブセットについてワイルドな推測をすることに消極的です。しかし、私と上記の説明の中で、私と他の人があなたを助けることができるように、あなたがつまずきを感じていることを具体的に言ってください。たとえば、定義したい特定の単語や、参照を追加したい概念はありますか?私は執筆の明快さとアクセシビリティを改善するのが本当に好きです。
Conal

27
「確定性」/「確定」とは、明確で明確な単一の値があることを意味します。対照的に、ほぼすべての形式の命令型同時実行では、スケジューラーや探しているかどうかに応じて、さまざまな答えが得られ、デッドロックになることさえあります。「セマンティック」(より具体的には「陳述」)は、「操作」(回答の計算方法、または何によってどれだけのスペースや時間が消費されるか)とは対照的に、式または表現の値(「表記」)を指します。マシンの一種)。
Conal

18
私は@ mindplay.dkに同意しますが、長い間フィールドにいたことを自慢することはできません。あなたは何を話しているのか知っているように見えましたが、これが何であるかを素早く簡単に理解することはできませんでした。この答えは主に、最初の質問に実際に答えることなく、たくさんの新しい質問に駆り立てました。この分野でまだ比較的無知であるという経験を共有することで、あなたが本当に必要なシンプルさと簡潔さについての洞察が得られることを期待しています。私はOPと同様の経歴を持っています。
Aske B. 2013

739

純粋な関数型プログラミングでは、副作用はありません。多くの種類のソフトウェア(たとえば、ユーザーとのやり取りがあるもの)では、ある程度の副作用が必要です。

関数スタイルを維持しながら副作用のような動作を得る1つの方法は、関数型反応型プログラミングを使用することです。これは、関数型プログラミングと反応型プログラミングの組み合わせです。(あなたがリンクしたウィキペディアの記事は後者についてです。)

リアクティブプログラミングの背後にある基本的な考え方は、「時間をかけて」値を表す特定のデータ型があるということです。これらの時間とともに変化する値を含む計算自体は、時間とともに変化する値を持ちます。

たとえば、マウスの座標を時間に対する整数値のペアとして表すことができます。次のようなものがあったとしましょう(これは疑似コードです):

x = <mouse-x>;
y = <mouse-y>;

いつでも、xとyはマウスの座標になります。非リアクティブプログラミングとは異なり、この割り当てを行う必要があるのは1回だけであり、x変数とy変数は自動的に「最新」のままになります。これが、リアクティブプログラミングと関数型プログラミングが非常にうまく連携する理由です。リアクティブプログラミングでは、変数を変更する必要がなくなり、変数の変更で実現できることの多くを実行できます。

次に、これに基づいていくつかの計算を行うと、結果の値も時間とともに変化する値になります。例えば:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

この例でminXは、は常にマウスポインターのx座標より16小さくなります。リアクティブ対応ライブラリを使用すると、次のように言うことができます。

rectangle(minX, minY, maxX, maxY)

そして、32x32のボックスがマウスポインターの周りに描画され、どこに移動しても追跡します。

これは、関数型反応型プログラミングに関するかなり良い論文です。


25
では、リアクティブプログラミングは宣言型プログラミングの一種なのでしょうか。
troelskn 2009年

31
>では、リアクティブプログラミングは宣言型プログラミングの一種なのでしょうか。 関数型反応型プログラミングは、関数型プログラミングの一種であり、宣言型プログラミングの一種です。
Conal

7
@ user712092いいえ、違います。たとえばsqrt(x)、マクロでC を呼び出すと、計算されsqrt(mouse_x())てdoubleが返されます。真の機能的反応システムでsqrt(x)は、新しい「時間の経過とともに」が返されます。FRシステムをシミュレートしようとした場合#define、マクロを優先して変数を断定する必要があります。また、FRシステムは通常、再計算が必要な場合にのみ再計算しますが、マクロを使用すると、部分式に至るまで、常にすべてを再評価することになります。
ローレンスゴンサルベス

4
「多くの種類のソフトウェア(たとえば、ユーザーとのやり取りがあるもの)では、ある程度の副作用が必要です。」そしておそらく実装レベルでのみ。純粋な遅延関数型プログラミングの実装には多くの副作用があり、パラダイムの成功の1つは、これらの影響の多くをプログラミングモデルから除外することです。機能的なユーザーインターフェイスへの私自身の進出は、副作用なしに完全にプログラムすることもできることを示唆しています。
Conal

4
@tieTYT xが再割り当て/変更されることはありません。xの値は、時間の経過に伴う値のシーケンスです。別の見方をすると、xが数値のような「通常の」値を持つ代わりに、xの値は(概念的には)パラメータとして時間がかかる関数です。(これは少し単純化しすぎています。マウスの位置などの未来を予測できる時間値を作成することはできません。)
Laurence Gonsalves 2013年

144

プログラムがスプレッドシートであり、すべての変数がセルであると想像することは、それがどのようなものかについて最初の直感に到達する簡単な方法です。スプレッドシートのセルのいずれかが変更されると、そのセルを参照するセルも変更されます。FRPと全く同じです。ここで、いくつかのセルが独自に変化する(つまり、外界から取得される)と想像してください。GUIの状況では、マウスの位置が良い例です。

それは必然的にかなり多くを逃します。実際にFRPシステムを使用すると、比喩はかなり速く分解します。1つは、通常、個別のイベントもモデル化しようとする試みです(マウスをクリックするなど)。私はこれをここに置いて、あなたにそれがどのようなものかを考えさせます。


3
非常に適切な例。理論的なものを持っていることは素晴らしいです、そしておそらく一部の人々は基礎となる例に頼ることなくその影響を得るかもしれません、しかし私はそれが私のために何をするかから始める必要があります。私が最近得たのは(NetflixによるRxの話から!)RP(またはとにかくRx)であり、これらの「値の変化」をファーストクラスにして、それらについて推論したり、それらを使って関数を作成したりできます。必要に応じて、スプレッドシートまたはセルを作成する関数を記述します。また、値が終了する(なくなる)ときに処理され、自動的にクリーンアップできます。
ベンジョン、2015年

この例では、インテリジェントなルーティングを使用するように依存関係を宣言するだけの、イベント駆動型プログラミングと事後対応型アプローチの違いを強調しています。
キンジェロム2017年

131

私にとってそれはシンボルの約2つの異なる意味です=

  1. 数学でx = sin(t)は、それxはの別の名前ですsin(t)。したがって、書き込みx + yはと同じですsin(t) + y。関数型反応プログラミングはこの点で数学に似ています。を記述しx + yた場合、t使用時の値が何であっても計算されます。
  2. C-ような言語(命令型言語)プログラミングでは、x = sin(t)割り当てである:その手段x店舗の値 sin(t)割当時に取ら。

5
良い説明。FRPの意味での「時間」は、通常「外部入力からの変更」であるということも追加できると思います。外力がFRPの入力を変更するときはいつでも、「時間」を前方に移動し、変更によって影響を受けるすべてを再計算します。
Didier A.

4
数学でx = sin(t)は平均xsin(t)与えられたの値ですt。as functionの別名ではありませんsin(t)。それ以外の場合はそうなりますx(t) = sin(t)
Dmitri Zaitsev

+ Dmitri Zaitsev等号は、数学においていくつかの意味を持っています。それらの1つは、左側を見たときに右側と入れ替えることができるということです。たとえば2 + 3 = 5またはa**2 + b**2 = c**2
user712092 16

71

OK、背景知識とあなたが指摘したWikipediaページを読むと、反応型プログラミングはデータフローコンピューティングのようなものですが、特定の外部「刺激」がノードのセットをトリガーしてそれらの計算を実行するようです。

これは、たとえばユーザーインターフェイスコントロール(たとえば、音楽再生アプリケーションの音量コントロール)に触れると、さまざまな表示項目と実際の音声出力の音量を更新する必要があるUI設計に非常に適しています。有向グラフのノードに関連付けられた値の変更に対応するボリューム(スライダーなど)を変更すると、

その「ボリューム値」ノードからのエッジを持つさまざまなノードが自動的にトリガーされ、必要な計算と更新がアプリケーション全体に波及します。アプリケーションはユーザーの刺激に「反応」します。関数型リアクティブプログラミングは、このアイデアを関数型言語で実装するか、または一般に関数型プログラミングパラダイム内で実装するだけです。

「データフローコンピューティング」の詳細については、Wikipediaで、またはお気に入りの検索エンジンを使用して、これら2つの単語を検索してください。一般的な考え方は次のとおりです。プログラムはノードの有向グラフであり、それぞれがいくつかの単純な計算を実行します。これらのノードは、いくつかのノードの出力を他のノードの入力に提供するグラフリンクによって互いに接続されています。

ノードが起動または計算を実行すると、その出力に接続されたノードには、対応する入力が「トリガー」または「マーク」されます。すべての入力がトリガーされた/マークされた/利用可能なすべてのノードが自動的に起動します。グラフは、リアクティブプログラミングの実装方法に応じて、暗黙的または明示的になる場合があります。

ノードは、並列に起動していると見なすことができますが、多くの場合、連続して実行されるか、並列処理が制限されています(たとえば、いくつかのスレッドがそれらを実行している場合があります)。有名な例は、マンチェスターデータフローマシンでした。これは、タグ付きデータアーキテクチャを使用して、1つ以上の実行ユニットを介してグラフ内のノードの実行をスケジュールしました。データフローコンピューティングは、計算をカスケードして非同期にトリガーする計算をトリガーして、実行を1つまたは複数のクロックで制御しようとするよりもうまく機能する状況に非常に適しています。

リアクティブプログラミングは、この「実行のカスケード」のアイデアをインポートし、データフローのような方法でプログラムを考えているようですが、一部のノードが「外界」にフックされ、これらの感覚のときに実行のカスケードがトリガーされます。のようなノードが変更されます。プログラムの実行は、複雑な反射弧に似たものになります。プログラムは、基本的に刺激間で固着している場合とそうでない場合があり、または刺激間で基本的に固着状態に落ち着く場合があります。

「非リアクティブ」プログラミングは、実行の流れと外部入力との関係について非常に異なる見方でプログラミングすることになります。人々はおそらく外部入力に反応する何かを彼らに「反応する」と言いたがるので、それは幾分主観的である可能性が高いです。しかし、本質を見ると、一定の間隔でイベントキューをポーリングし、見つかったイベントを関数(またはスレッド)にディスパッチするプログラムは、反応性が低くなります(一定の間隔でのみユーザー入力に対応するため)。繰り返しますが、これはここでの精神です。高速なポーリング間隔のポーリング実装をシステムに非常に低いレベルで配置し、その上に反応的にプログラミングすることを想像できます。


1
OK、上にいくつかの良い答えがあります。投稿を削除しますか?2人または3人が何も追加しないと言っているのを見つけた場合、その有用なカウントが増加しない限り、削除します。何か価値のあるものを追加しない限り、ここを離れても意味がありません。
Thomas Kammeyer、

3
あなたはデータフローについて言及したので、それは私見にいくつかの価値を追加します。
ライナージョスウィグ2009年

それがQMLの
本来の目的

3
私にとって、この答えは最も理解しやすいものでした。特に、「アプリケーションを介して波打つ」や「感覚のようなノード」などの自然な類似体を使用しているためです。すごい!
AkseliPalén2015

1
残念ながら、Manchester Dataflow Machineリンクは機能していません。
Pac0 2017年

65

FRPに関する多くのページを読んだ後、私はようやくFRPに関するこの啓発的な執筆に出くわしました、それは最終的に私はFRPが本当にすべてについて何であるかを理解するようになりました。

ハインリッヒ・アフェルムス(反応性バナナの作者)の下に引用します。

関数型反応型プログラミングの本質は何ですか?

よくある答えは、「FRPはシステムを可変状態ではなく時変関数で表現することだ」であり、それは間違いなく間違いではありません。これが意味論的視点です。しかし、私の意見では、より深く、より満足のいく答えは、次の純粋な構文基準によって与えられます。

関数型反応型プログラミングの本質は、宣言時に値の動的な動作を完全に指定することです。

たとえば、カウンターの例を考えてみましょう。「アップ」および「ダウン」というラベルの付いた2つのボタンがあり、これらを使用してカウンターを増分または減分できます。命令的には、最初に初期値を指定し、次にボタンが押されるたびにそれを変更します。このようなもの:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)

重要なのは、宣言の時点では、カウンターの初期値のみが指定されているということです。カウンターの動的な動作は、プログラムテキストの残りの部分では暗黙的です。対照的に、関数型反応型プログラミングでは、宣言時に動的な動作全体を次のように指定します。

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)

カウンターのダイナミクスを理解したいときはいつでも、カウンターの定義を見れば十分です。それに起こり得るすべてが右側に表示されます。これは、後続の宣言で以前に宣言された値の動的な動作を変更できる命令型アプローチとは非常に対照的です。

したがって、私の理解では、FRPプログラムは一連の方程式です。 ここに画像の説明を入力してください

j 離散的です:1,2,3,4 ...

f依存しt、これは外部からの刺激をモデル化するためにpossibliltyを取り入れて

プログラムのすべての状態が変数にカプセル化されます x_i

FRPライブラリは、時間の経過、つまりにかかる時間を処理jj+1ます。

これらの方程式については、このビデオでさらに詳しく説明します。

編集:

元の回答から約2年後、最近、FRPの実装には別の重要な側面があるという結論に達しました。彼らは重要な実際的な問題であるキャッシュの無効化を解決する必要があります(通常は解決します)。

x_i-s の方程式は、依存関係グラフを表します。いくつかの場合にはx_i時の変更はj、他のすべてのいないx_i'時の値j+1必要が更新される、そうではないすべての依存関係は、いくつかのために再計算する必要があるx_i'から独立したかもしれませんx_i

さらに、x_i変更を行う-sは増分更新できます。たとえばのは、マップ操作考えるf=g.map(_+1)スカラ座、中fgしているListのをInts。ここfに対応x_i(t_j)してgいますx_j(t_j)。要素を前に追加すると、のすべての要素に対して操作gを実行するのは無駄になります。一部のFRP実装(たとえばreflex-frp)は、この問題の解決を目的としています。この問題は、インクリメンタルコンピューティングとも呼ばれます。mapg

つまり、x_iFRPの動作(-s)は、キャッシュされた計算と考えることができます。x_i-sの一部がf_i変更された場合にこれらのキャッシュ(-s)を効率的に無効化および再計算するのはFRPエンジンのタスクです。


4
あなたが離散方程式を使うまで、私はあなたと一緒にいました。FRPの創設思想は、「」のない連続した時間j+1でした。代わりに、連続時間の機能について考えてください。ニュートン、ライプニッツなどが示したように、ODEの積分とシステムを使用してこれらの関数を微分的に、しかし連続的に記述することは、しばしば非常に便利です(そして文字通り「自然」です)。それ以外の場合は、モノ自体ではなく、近似アルゴリズム(および貧弱なアルゴリズム)を記述しています。
Conal

HTMLテンプレートおよびレイアウト制約言語layxは、FRPの要素を表現しているようです。

@Conalでは、FRPがODEとどのように異なるのか不思議に思います。それらはどう違うのですか?
jhegedus 2017

@jhegedusその統合(おそらく再帰的、つまりODE)では、全体ではなくFRPのビルディングブロックの1つが提供されます。FRP語彙のすべての要素(統合を含むがこれに限定されない)は、連続時間の観点から正確に説明されます。その説明は役に立ちますか?
Conal 2017


29

免責事項:私の答えは、JavaScript用の「リアクティブプログラミング」ライブラリであるrx.jsのコンテキストにあります。

関数型プログラミングでは、コレクションの各アイテムを反復処理する代わりに、高次関数(HoF)をコレクション自体に適用します。したがって、FRPの背後にある考え方は、個々のイベントを処理する代わりに、(observable *で実装された)イベントのストリームを作成し、代わりにそれにHoFを適用することです。このようにして、パブリッシャーをサブスクライバーに接続するデータパイプラインとしてシステムを視覚化できます。

オブザーバブルを使用する主な利点は次のとおりです
。i)コードから状態を抽象化します。たとえば、イベントハンドラーをn番目のイベントごとにのみ発生させる場合や、最初のnイベントの後に発生を停止する場合または、最初の「n」イベントの後にのみ起動を開始する場合は、カウンターの設定、更新、チェックの代わりに、HoF(フィルター、takeUntil、スキップ)を使用できます。
ii)コードの局所性が向上します-コンポーネントの状態を変更する5つの異なるイベントハンドラーがある場合、それらのオブザーバブルをマージし、マージされたオブザーバブルに単一のイベントハンドラーを定義して、5つのイベントハンドラーを1に効果的に組み合わせることができます。システム全体のどのイベントがコンポーネントに影響を与える可能性があるかについては、すべて単一のハンドラーに存在するため、簡単に推論できます。

  • ObservableはIterableのデュアルです。

Iterableは、遅延して消費されるシーケンスです。各項目は、使用するたびにイテレーターによってプルされるため、列挙はコンシューマーによって駆動されます。

オブザーバブルは遅延生成されたシーケンスです。各アイテムはシーケンスに追加されるたびにオブザーバーにプッシュされるため、列挙はプロデューサーによって駆動されます。


1
オブザーバブルのこの簡単な定義と、イテラブルとの区別に感謝します。真の理解を得るために、複雑な概念をそのよく知られた二重概念と比較することはしばしば非常に役立つと思います。

2
「したがって、FRPの背後にある考え方は、個々のイベントを処理する代わりに、(observable *で実装された)イベントのストリームを作成し、代わりにそれにHoFを適用することです。」私は誤解しているかもしれませんが、これは実際にはFRPではなく、命令型コードで使用することを引き続き意図しながら、HoFを介した機能操作を可能にするObserver設計パターンの優れた抽象化であると思います。トピックに関するディスカッション-lambda-the-ultimate.org/node/4982
nqe

18

おい、これは驚くべき素晴らしいアイデアです!1998年にこのことを知らなかったのはなぜですか。とにかく、これがフランのチュートリアルの私の解釈です。提案は大歓迎です。私はこれに基づいてゲームエンジンを起動することを考えています。

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

つまり、すべてのコンポーネントを数値のように扱うことができれば、システム全体を数学の方程式のように扱うことができるでしょう。


1
これは少し遅いですが、とにかく... FragはFRPを使用したゲームです。
arx 2013年


10

以前の回答によると、数学的には、単純に高次で考えるようです。タイプXを持つ値xを考える代わりに、関数xTXを考えます。ここで、Tは自然数、整数、連続体のいずれであっても、時間のタイプです。ここで、プログラミング言語でy:= x + 1 と書くとき、実際には方程式yt)= xt)+ 1を意味します。


9

前述のとおり、スプレッドシートのように機能します。通常、イベント駆動型フレームワークに基づいています。

すべての「パラダイム」と同様に、その新しさは議論の余地があります。

アクターの分散フローネットワークの私の経験から、それはノードのネットワーク全体の状態の一貫性の一般的な問題の餌食になる可能性があります。つまり、多くの振動と奇妙なループのトラッピングに終わります。

一部のセマンティクスは参照ループまたはブロードキャストを意味するため、これを回避するのは難しく、アクターのネットワークが予測できない状態に収束する(または収束しない)ため、非常に無秩序になる可能性があります。

同様に、エッジが明確に定義されていても、グローバル状態がソリューションから離れているため、一部の状態に到達しない場合があります。2 + 2は、2が2になったとき、および2がそのままだったかどうかに応じて、4になる場合とされない場合があります。スプレッドシートには、同期クロックとループ検出があります。分散型アクターは通常はそうしません。

すべて楽しい:)。



7

Andre Staltzによるこの記事は、私がこれまでに見た中で最もわかりやすい説明です。

記事からの引用:

リアクティブプログラミングは、非同期データストリームを使用したプログラミングです。

その上で、これらのストリームを結合、作成、およびフィルタリングするための機能の素晴らしいツールボックスが提供されます。

以下は、記事の一部である素晴らしい図の例です。

クリックイベントストリーム図


5

時間の経過に伴う(または時間を無視した)数学的データ変換についてです。

コードでは、これは機能の純粋さと宣言型プログラミングを意味します。

状態のバグは、標準の命令型パラダイムでは大きな問題です。コードのさまざまなビットにより、プログラムの実行のさまざまな「時点」で共有状態が変更される場合があります。これは対処するのが難しいです。

FRPでは、(宣言型プログラミングのように)データがある状態から別の状態にどのように変換され、何がそれをトリガーするかを記述します。関数は単に入力に反応し、現在の値を使用して新しい値を作成するため、時間を無視できます。これは、状態が変換ノードのグラフ(またはツリー)に含まれ、機能的に純粋であることを意味します。

これにより、複雑さとデバッグ時間が大幅に削減されます。

数学のA = B + CとプログラムのA = B + Cの違いを考えてみてください。数学では、決して変わらない関係を説明しています。プログラムでは、「今」AはB + Cであると述べています。しかし、次のコマンドはB ++になる可能性があります。その場合、AはB + Cと等しくありません。数学または宣言型プログラミングでは、どの時点で質問しても、Aは常にB + Cに等しくなります。

そのため、共有状態の複雑さを排除し、時間の経過とともに値を変更します。あなたのプログラムは推論するのがはるかに簡単です。

EventStreamは、EventStream +いくつかの変換関数です。

Behaviorは、EventStream +メモリ内のいくつかの値です。

イベントが発生すると、変換関数を実行して値が更新されます。これが生成する値は、動作メモリに格納されます。

動作を構成して、他のN個の動作を変換する新しい動作を生成できます。この合成値は、入力イベント(動作)が発生すると再計算されます。

「オブザーバーはステートレスであるため、ドラッグの例のように、ステートマシンをシミュレートするためにオブザーバーが必要になることがよくあります。上記の変数パスなど、関係するすべてのオブザーバーがアクセスできる状態を保存する必要があります。」

引用-オブザーバーパターンの廃止 http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf


これがまさに宣言型プログラミングについて私が感じる方法であり、あなたは私よりもアイデアをよりよく説明しています。
neevek 2018

2

リアクティブプログラミングに関する短くて明確な説明がCyclejs-Reactive Programmingに表示されます。シンプルで視覚的なサンプルを使用しています。

[モジュール/コンポーネント/オブジェクト] は事後対応であり、外部イベントに対応することによって自身の状態を管理する責任があります。

このアプローチの利点は何ですか?これは主に[モジュール/コンポーネント/オブジェクト]が自分自身に責任を負うため、コントロールの反転であり、パブリックメソッドに対するプライベートメソッドを使用してカプセル化を改善します。

これは、良い出発点であり、知識の完全なソースではありません。そこから、より複雑で深い論文にジャンプできます。


0

Rx、.NET用のReactive Extensionsを確認してください。彼らは、IEnumerableを使用すると、基本的にストリームから「プル」することを指摘しています。IQueryable / IEnumerableに対するLinqクエリは、セットから結果を「吸い込む」セット演算です。しかし、IObservableで同じ演算子を使用すると、「反応する」Linqクエリを記述できます。

たとえば、(MyObservableSetOfMouseMovementsのmからmX <100およびmY <100が新しいPoint(mX、mY)を選択する)のようなLinqクエリを記述できます。

そして、Rx拡張機能を使用すれば、それだけです。マウスの動きの着信ストリームに反応して、100、100ボックスにいるときはいつでも描画するUIコードがあります...


0

FRPは、関数型プログラミング(すべてが機能であるという考えに基づいて構築されたプログラミングパラダイム)とリアクティブプログラミングパラダイム(すべてがストリームであるという考えに基づいて構築された(オブザーバーおよび観察可能な哲学))の組み合わせです。世界最高のはずです。

まず、反応型プログラミングに関するAndre Staltzの投稿をご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.