リアクティブプログラミング についてのウィキペディアの記事を読みました。 関数型リアクティブプログラミング についての小さな記事も読んだことがあります。説明は非常に抽象的です。
私の経歴は命令型/オブジェクト指向言語であるため、このパラダイムに関連した説明はお勧めです。
FRPの雰囲気を味わいたい場合は、1998年の古い Fran tutorial から始めることができます。論文の場合、 Functional Reactive Animation で始めてから、ホームページの出版物リンクのリンクをフォローアップし、 FRPHaskell wiki のリンク。
個人的には、FRPの意味について考えてから、その実装方法を説明します。 (仕様のないコードは質問のない答えであり、したがって「間違っていません」)。したがって、トーマスKが別の答え(グラフ、ノード、エッジ、起動、実行、等)。可能な実装スタイルは多数ありますが、FRPisが何であるかについての実装はありません。
私は、FRPが「「経時的な」値を表すデータ型」に関するものであるというローレンスGの簡単な説明に共鳴します。従来の命令型プログラミングは、これらの動的な値を、状態および突然変異を通じて間接的にのみキャプチャします。完全な履歴(過去、現在、未来)には、ファーストクラスの表現はありません。さらに、命令パラダイムは時間的に離散的であるため、離散的に進化する値のみを(間接的に)キャプチャできます。対照的に、FRPはこれらの進化する値を直接キャプチャし、継続的に進化する価値。
FRPはまた、命令的並行性を害する理論的および実用的なネズミの巣に反することなく並行しているという点でも異常です。意味的には、FRPの並行性はfine-grained、determinate、およびcontinuous。 (私は実装についてではなく、意味について話している。実装は、並行性または並列性を伴う場合も伴わない場合もある。)意味的決定性は、厳密かつ非公式の推論にとって非常に重要である。並行性は、非決定的インターリーブのために、命令型プログラミングに非常に複雑さを追加しますが、FRPでは簡単です。
それでは、FRPとは何ですか?自分で発明することもできます。これらのアイデアから始めます。
動的/進化する値(つまり、「経時的な」値)は、それ自体が第一級の値です。それらを定義して結合し、関数に渡すことができます。私はこれらのことを「行動」と呼びました。
振る舞いは、一定の(静的な)振る舞いと時間(時計のような)のようないくつかのプリミティブから構築され、その後、順次および並列の組み合わせで行われます。 n動作は、n値関数(静的な値)を「ポイントごと」に、つまり時間をかけて連続的に適用することで結合されます。
個別の現象を説明するために、別のタイプ(ファミリ)の「イベント」を用意します。各イベントには、ストリーム(有限または無限)の発生があります。各オカレンスには、時間と値が関連付けられています。
すべての動作とイベントを構築できる構成語彙を思い付くには、いくつかの例を試してください。より一般的でシンプルな部分に分解してください。
あなたがしっかりと地にいることを知っているように、モデル全体に表示的セマンティクスの手法を使用して構成の基礎を与えます。これは、(a)各タイプが「意味」の対応するシンプルで正確な数学タイプを持ち、 b)各プリミティブと演算子は、構成要素の意味の関数として単純で正確な意味を持ちます。 決して、ever実装の考慮事項を探索プロセスに混在させないでください。この説明がわかりにくい場合は、(a)型クラスモルフィズムの表示設計、(b)プッシュプル機能リアクティブプログラミング(実装ビットを無視)、および(c) 表記法Haskell wikibooksページ 。デノテーショナルセマンティクスには、2つの創始者Christopher StracheyとDana Scottの2つの部分があることに注意してください。より簡単で便利なStrachey部分と、より困難で有用性の低い(ソフトウェア設計)Scott部分です。
これらの原則に固執すれば、FRPの精神で多少なりとも何かが得られると期待しています。
これらの原則はどこで入手しましたか?ソフトウェア設計では、私はいつも同じ質問をします:「それはどういう意味ですか?」。デノテーショナルセマンティクスは、この質問の正確なフレームワークを提供し、私の美学に合ったフレームワークを提供しました(操作的または公理的セマンティクスとは異なり、どちらも私を満足させません)。それで、私は行動とは何かを自問しましたか?命令型計算の時間的に離散的な性質は、動作自体の自然な説明ではなく、特定のスタイルmachineへの適応であることにすぐに気付きました。私が考えることができる振る舞いの最も単純で正確な記述は、単純に「(連続的な)時間の機能」なので、それが私のモデルです。喜ばしいことに、このモデルは連続的で決定的な同時実行を簡単かつ優雅に処理します。
このモデルを正しく効率的に実装することは非常に困難でしたが、それは別の話です。
純粋な関数型プログラミングでは、副作用はありません。多くの種類のソフトウェア(たとえば、ユーザーとの対話があるもの)では、何らかのレベルで副作用が必要です。
関数型のスタイルを維持しながら副作用のような振る舞いをする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)
そして32×32のボックスがマウスポインタの周りに描かれ、どこに動いてもそれを追跡します。
これはかなり良い 関数型リアクティブプログラミングに関する論文です 。
それがどんなものであるかについての最初の直感に達する簡単な方法はあなたのプログラムがスプレッドシートであり、あなたの変数のすべてがセルであると想像することです。スプレッドシート内のいずれかのセルが変更されると、そのセルを参照するセルも変更されます。 FRPでも同じです。ここで、セルの中には自分自身で変化するものがあると想像してみてください(あるいは、外界から取られたものです)。
それは必然的にかなり逃します。あなたが実際にFRPシステムを使用するとき、隠喩はかなり速く崩壊します。 1つには、通常、離散的なイベントもモデル化する試みがある(例えば、マウスがクリックされている)。私はあなたにそれがどんなものかについての考えを与えるためにこれをここに置いているだけです。
私にとっては、シンボル=
の2つの異なる意味です。
x = sin(t)
はx
が 別名 sin(t)
のことを意味します。したがって、x + y
を書くことはsin(t) + y
と同じことです。関数リアクティブプログラミングは、この点では数学に似ています。x + y
を書くと、使用時のt
の値が何であっても計算されます。x = sin(t)
が代入です:これはx
が代入時に取った 値 sin(t)
を格納することを意味します。背景知識から、そしてあなたが指摘したウィキペディアのページを読むことから、リアクティブプログラミングはデータフローコンピューティングのようなものですが、特定の外部「刺激」がノードのセットを起動してそれらの計算を実行させるものです。
これは、UIデザインに非常に適しています。たとえば、ユーザーインターフェイスコントロール(音楽再生アプリケーションの音量コントロールなど)をタッチすると、さまざまな表示項目と実際の音声出力の音量を更新する必要がある場合があります。有向グラフ内のノードに関連付けられている値を変更することに対応するボリューム(スライダなど)を変更するとします。
その「ボリューム値」ノードからのエッジを持つさまざまなノードが自動的にトリガーされ、必要な計算や更新がアプリケーション全体に波及します。アプリケーションはユーザーの刺激に「反応」します。関数型リアクティブプログラミングは、単にこの概念を関数型言語で、または一般的には関数型プログラミングパラダイム内で実装することです。
「データフローコンピューティング」の詳細については、ウィキペディアで2つの単語を検索するか、お気に入りの検索エンジンを使用してください。一般的な考え方はこれです:プログラムはノードの有向グラフで、それぞれが簡単な計算を実行します。これらのノードは、いくつかのノードの出力を他のノードの入力に提供するグラフリンクによって互いに接続されています。
ノードが起動または計算を実行すると、その出力に接続されているノードに対応する入力が「トリガー」または「マーク」されます。すべての入力がトリガーされた/マークされた/利用可能なすべてのノードが自動的に起動します。グラフは、リアクティブプログラミングの実装方法に応じて、暗黙的または明示的になることがあります。
ノードは並行して起動するものと見なすことができますが、多くの場合、それらはシリアルに実行されるか、限られた並列処理で実行されます(たとえば、それらを実行するスレッドがいくつかある場合があります)。有名な例は Manchester Dataflow Machine で、これはタグ付きデータアーキテクチャを使用して、グラフ内のノードの実行を1つ以上の実行単位でスケジュールします。データフローコンピューティングは、非同期的に計算のカスケードを発生させるトリガを実行することを1つまたは複数のクロックで実行しようとするよりもうまく機能する状況に非常に適しています。
リアクティブプログラミングは、この「実行のカスケード」という考えをインポートし、データフローのようにプログラムを考えているように見えますが、一部のノードは「外部の世界」にフックされ、実行のカスケードはこれらの感覚によってトリガされます。のようなノードが変わります。プログラムの実行は複雑な反射弧に似たものになります。プログラムは、刺激間で基本的に固着してもしなくてもよく、または刺激間で基本的に固着状態に落ち着いてもよい。
「無反応」プログラミングとは、実行の流れと外部入力との関係についてまったく異なる見方をしたプログラミングです。外部からの入力に反応するものはすべて「反応する」と人々が言うことになりがちなので、やや主観的に思われるでしょう。しかし、その精神を見ると、イベントキューを一定の間隔でポーリングし、見つかったすべてのイベントを関数(またはスレッド)にディスパッチするプログラムは、反応性が低くなります(一定の間隔でユーザー入力にのみ対応するため)。繰り返しますが、それがここにあるものの精神です:非常に低いレベルでシステムにポーリング間隔の速いポーリングの実装を入れることを想像でき、その上で反応的な方法でプログラムできます。
FRPについて多くのページを読んだ後、ようやく出会った this FRPについての啓発的な文章で、最終的にFRPの本当の意味を理解しました。
ハインリッヒ・アプフェルムス(反応性バナナの著者)を引用します。
関数型リアクティブプログラミングの本質は何ですか?
一般的な答えは、「FRPは、可変状態ではなく時変関数の観点からシステムを記述することに関するものです」ということであり、間違いなく間違いないでしょう。これはセマンティックの観点です。しかし、私の意見では、より深く、より満足のいく答えは、次の純粋に構文的な基準によって与えられます:
関数型リアクティブプログラミングの本質は、宣言時に値の動的な振る舞いを完全に指定することです。
たとえば、カウンターの例を考えてみましょう。「Up」と「Down」というラベルの付いた2つのボタンがあり、これらのボタンを使用してカウンターをインクリメントまたはデクリメントできます。必須として、最初に初期値を指定し、ボタンが押されるたびにそれを変更します。このようなもの:
counter := 0 -- initial value on buttonUp = (counter := counter + 1) -- change it later on buttonDown = (counter := counter - 1)
ポイントは、宣言時には、カウンターの初期値のみが指定されるということです。 counterの動的な動作は、プログラムテキストの残りの部分で暗黙的です。対照的に、関数型リアクティブプログラミングでは、次のように宣言時の動的な動作全体を指定します。
counter :: Behavior Int counter = accumulate ($) 0 (fmap (+1) eventUp `union` fmap (subtract 1) eventDown)
カウンターのダイナミクスを理解したいときはいつでも、その定義を見るだけです。それに起こりうるすべてが右側に表示されます。これは、後続の宣言が以前に宣言された値の動的な動作を変更できる命令型アプローチとは非常に対照的です。
したがって、私の理解では、FRPプログラムは方程式のセットです:
j
は離散:1,2,3,4 ...
f
はt
に依存するため、外部刺激をモデル化する可能性が組み込まれています。
プログラムのすべての状態は変数x_i
にカプセル化されます
FRPライブラリは、進行時間、つまりj
からj+1
を処理します。
this videoでこれらの方程式をより詳細に説明します。
編集:
最初の答えから約2年後、最近、FRPの実装には別の重要な側面があるという結論に達しました。彼らは、重要な実用的な問題を解決する必要があります(そして通常):cache invalidation。
x_i
- sの方程式は、依存関係グラフを表します。 x_i
の一部がj
の時点で変更される場合、x_i'
の他のj+1
値をすべて更新する必要はないため、一部の依存関係を再計算する必要はありませんx_i'
はx_i
から独立している場合があります。
さらに、変更を行うx_i
- sは、インクリメンタルに更新できます。たとえば、Scalaのマップ操作f=g.map(_+1)
を考えてみましょう。f
とg
はList
のInts
です。ここで、f
はx_i(t_j)
に対応し、g
はx_j(t_j)
です。ここで要素をg
の先頭に追加すると、map
のすべての要素に対してg
操作を実行するのは無駄になります。一部のFRP実装(たとえば reflex-frp )は、この問題を解決することを目的としています。この問題は incremental computing。 とも呼ばれます
つまり、FRPの動作(x_i
- s)はキャッシュされた計算と考えることができます。 x_i
- sの一部が変更された場合、これらのキャッシュ(f_i
- s)を効率的に無効化および再計算することはFRPエンジンのタスクです。
免責事項:私の答えはrx.js - Javascript用の「リアクティブプログラミング」ライブラリのコンテキストにあります。
関数型プログラミングでは、コレクションの各項目を反復処理する代わりに、コレクション自体に高階関数(HoF)を適用します。したがって、FRPの背後にある考え方は、個々のイベントを処理する代わりに、(観測可能な*で実装された)イベントのストリームを作成し、その代わりにHoFを適用することです。このようにして、発行者と購読者を結ぶデータパイプラインとしてシステムを視覚化できます。
観測量を使用する主な利点は次のとおりです。
i)コードから状態を抽象化します。たとえば、イベントハンドラを 'n'番目のイベントごとにのみ発生させる、または最初の 'n'イベントの後で発生を停止する、または発生のみを開始する場合最初の 'n'イベントの後は、カウンタの設定、更新、確認の代わりにHoF(それぞれfilter、takeUntil、skip)を使用することができます。
ii)コードの局所性を向上させる - コンポーネントの状態を変更する5つの異なるイベントハンドラがある場合は、それらのオブザーバブルをマージし、マージされたオブザーバブルに単一のイベントハンドラを定義するシステム全体のどのイベントが1つのハンドラ内に存在するため、コンポーネントに影響を与える可能性があるかを判断するのは非常に簡単です。
Iterableは遅延的に消費されるシーケンスです。各アイテムは使用するたびにイテレータによって取得されるため、列挙はコンシューマによって駆動されます。
オブザーバブルは遅延生成シーケンスです。各アイテムはシーケンスに追加されるたびにオブザーバにプッシュされるため、列挙はプロデューサによって駆動されます。
Conal Elliottによる単純に効率的な機能的反応性( direct PDF 、233 KB)は、かなり良い紹介です。対応するライブラリも動作します。
この論文は現在、別の論文に置き換えられています。プッシュプル関数型リアクティブプログラミング( direct PDF 、286 KB)。
おい、これはおかしいの素晴らしいアイデアです! 1998年になぜ私がこれについて知らなかったのですか?とにかく、これが Fran チュートリアルの私の解釈です。提案は大歓迎です、私はこれに基づいてゲームエンジンを始動することを考えています。
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()
一言で言えば、すべての要素を数値のように扱うことができれば、システム全体を数学の方程式のように扱うことができます。
Paul Hudakの著書、 The Haskell School of Expression は、Haskellの入門書であるだけでなく、FRPにかなりの時間を費やしています。あなたがFRPの初心者であれば、FRPがどのように機能するのかを理解できるようにすることを強くお勧めします。
この本を新しく書き換えたように見えるものもあります(2011年リリース、2014年更新)、 The Haskell School of Music 。
以前の回答によれば、数学的には、私たちは単純に高次で考えるようです。型Xを持つ値xを考える代わりに、関数xを考える:T→X、ここでTは時間の種類です。自然数、整数、連続体のいずれでもかまいません。プログラミング言語でy:= x + 1と書くと、実際には方程式y(t)= x(t)を意味します。)+ 1.
前述のようにスプレッドシートのように機能します。通常、イベント駆動型フレームワークに基づいています。
すべての「パラダイム」と同様に、その新しさは議論の余地があります。
私のアクターの分散フローネットワークの経験から、それはノードのネットワーク全体にわたる状態の一貫性の一般的な問題には簡単に陥ることができます。
意味論の中には参照ループやブロードキャストを意味するものがあるため、これを避けるのは困難です。また、アクターのネットワークが予測不可能な状態に収束する(または収束しない)ため、混沌とすることがあります。
同様に、大域的な状態は解から遠ざかるため、明確に定義されたエッジを持っているにもかかわらず、いくつかの状態に到達できないかもしれません。 2 + 2は、2が2になった時期、およびそのままだったかどうかによって、4になる場合とそうでない場合があります。スプレッドシートには同期クロックとループ検出があります。分散アクターは一般的にそうではありません。
とても楽しかったです:)。
FRPについてのClojure subredditで、このNiceビデオを見つけました。あなたがClojureを知らなくてもそれは理解するのがかなり簡単です。
これがビデオです: http://www.youtube.com/watch?v=nket0K1RXU4
これはビデオが後半に参照するソースです: https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs
この記事 Andre Staltz著これまで私が見た中で最も明確でわかりやすい説明です。
記事からのいくつかの引用:
リアクティブプログラミングは、非同期データストリームを使用したプログラミングです。
それに加えて、あなたはそれらのストリームのどれでも結合し、作成しそしてフィルターにかけるための機能の驚くべきツールボックスを与えられます。
これは記事の一部である幻想的な図の例です:
それは、経時的な(または時間を無視した)数学的データ変換に関するものです。
コードでは、これは機能的純粋さと宣言型プログラミングを意味します。
州のバグは、標準の命令型パラダイムにおける大きな問題です。さまざまなコードのビットが、プログラムの実行中に異なる「時間」で共有状態を変更することがあります。これは対処が難しいです。
FRPでは、(宣言型プログラミングの場合のように)データがある状態から別の状態にどのように変換され、それが何を引き起こすのかを記述します。あなたの関数は単にその入力に反応して新しい値を作成するためにそれらの現在の値を使うので、これはあなたが時間を無視することを可能にします。これは、状態が変換ノードのグラフ(またはツリー)に含まれており、機能的に純粋であることを意味します。
これにより、複雑さとデバッグ時間が大幅に削減されます。
数学のA = B + CとプログラムのA = B + Cの違いを考えてください。数学では、決して変わらない関係について説明しています。プログラムでは、それは "今すぐ" AはB + Cであると言います。しかし、次のコマンドはB ++かもしれません。その場合、AはB + Cと等しくありません。数学的または宣言的プログラミングでは、どの時点で尋ねても、Aは常にB + Cに等しくなります。
そのため、共有状態の複雑さを取り除き、時間の経過とともに値を変更します。あなたのプログラムは推論するのがはるかに簡単です。
EventStreamはEventStream +何らかの変換関数です。
ビヘイビアはEventStream +メモリ内の値です。
イベントが発生すると、変換関数を実行して値が更新されます。これが生み出す値はビヘイビアメモリに保存されます。
ビヘイビアは、他のN個のビヘイビアへの変換である新しいビヘイビアを生成するように構成できます。この合成値は、入力イベント(ビヘイビア)が発生すると再計算されます。
「オブザーバはステートレスであるため、ドラッグの例のようにステートマシンをシミュレートするためにはオブザーバのうちのいくつかが必要になります。
からの引用 - オブザーバパターンの非推奨 http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf
Reactive Programmingについての短くて明確な説明は Cyclejs - Reactive Programming にあります。これは単純で視覚的なサンプルを使っています。
[module/Component/object] は反応的です は外部のイベントに反応することによってそれ自身の状態を管理する責任があることを意味します。
このアプローチの利点は何ですか?これは コントロールの反転 です。これは、主に[module/Component/object]がそれ自体に責任があるためです。
これは良い出発点であり、完全な知識の源ではありません。そこから、もっと複雑で深い論文にジャンプすることができます。
FRPは関数型プログラミング(すべてが関数であるという考えに基づいて構築されたプログラミングパラダイム)と(すべてがストリームであるという考えに基づいて構築された(観察者および観察可能な哲学))パラダイムの組み合わせです。それは世界最高のものになるはずです。
まず最初にAndre Staltzによるリアクティブプログラミングの投稿をチェックしてください。
Rx、Reactive Extensions for .NETを調べてください。彼らは、IEnumerableを使用すると、基本的にストリームから「引っ張っている」ことを指摘しています。 IQueryable/IEnumerableを介したLinqクエリは、セットから結果を「吸い出す」セット操作です。しかし、IObservable上の同じ演算子を使えば、「反応する」Linqクエリを書くことができます。
たとえば、(MyObservableSetOfMouseMovementsのmからm.X <100およびm.Y <100でnew Point(m.X、m.Y)を選択する)のようなLinqクエリを作成できます。
そして、Rx拡張では、それはそれです:あなたは、マウスの動きの入ってくるストリームに反応し、あなたが100,100ボックスにいるときはいつでも描くUIコードを持っています...