やさしいFunctional reactive programming(概要編)

あと、やはりネットワーク周りなどI/Oの多いプログラムの書きにくさが課題になっている印象。関数的なI/OはFRPで解決できそうな気がするんだけど調べてない。そろそろFRPをちゃんと理解したいなー。

Parsec 3活用事例: Keepalived構文チェッカ - maoeのブログ

なんて書いてから早1ヶ月半、ようやくFRPが掴めてきたのでわかったことをまとめてみます。

Reactive programmingって何?

FRPの前に、一般的にwikipedia:en:Reactive programmingと呼ばれるパラダイムについて触れておきます。reactive programmingとは疑似言語を使ってかなーり大雑把に説明すると、

var a = 1
var b = a + 1
a = 10        // aを書き換える
print b       // => 11

print bの出力は2ではなく11です。aの値が変わったからbの値が動的に変化したのです。別のもっと端的な例を出すと、

var t = time   // 現在のエポック秒を動的に表す変数
print (t * 2)  // => 現在のエポック秒を2倍

なんてことができたりもします。

要するに、時間や外部の入力とともに変化する値や計算を、ユーザ自身がプログラムするのではなく、システム(言語自体やライブラリ)がユーザからは見えないところで反応(reactive)してくれるというものです。一見すると何の役に立つのかわかりませんが、reactive programmingによって一部のプログラムが劇的に書きやすくなることがあります。よくある応用先としては、

  • アニメーションやコンピュータミュージックのシグナル処理を自然に書ける
  • イベントドリブンなコードで処理がぶつ切りになってしまうのを避ける
    • GUIプログラミングとか
    • ロボット制御とか

などが挙げられます。

実のところreactive programmingは世間一般に広く使われています。しかもプログラマにではなく、一般のPC利用者にです。それはExcelです。Excelでは、セルA1に「=B1+C1」と書くとB1とC1が加算され、A1の内容は動的に書き換えられます。

参考情報

reactive programmingについての日本語の情報は

あたりが良いかもしれません。

他にもwikipedia:en:Reactive programmingに代表的な実装例が載っています。

Functional reactive programming(FRP)って何?

さて、このreactive programmingを関数型の世界に持ってきたものがwikipedia:en:Functional reactive programming(FRP)です。

Wikidediaから抜粋するとFRPにおいて重要なポイントは次の通りです。

  • 入力はbehavior(振る舞いとかビヘイビア)か時間で変化するイベントストリームとして見える
  • 時間にともない連続的(continuous)に変化する値を扱える(これがbehavior)
  • 時間順に並ぶ離散的(discrete)なevent
  • 時間にともない変化する値は、higer orderかもしれない(要するに普通の値じゃないかもしれない)

まるで何を言っているのかわかりませんが、簡単に言うとbehaviorとeventという二つの重要な概念があると言うことです。それぞれわかりやすい例を挙げます。

behaviorの一番簡単な例は時間です。ある値tが時間を表すbehaviorだとすると、値tは時とともに連続的に変化します。tを出力するとある時点の時刻が(サンプリングされて)表示されますが、内部的には連続的に変化しています。他にもユーザが動かしているマウスの座標だったり、日照によって変化するボンネットの温度や、振り子運動しているブランコの速度、刻々と変化する株価など、behaviorは実にありふれています。

一方で、eventとは時間と値が組になったストリームです。たとえばユーザからのキー入力を表すkeyという値を考えます。keyはユーザがキーを押した時刻tと、どのキーかを表すcを組にした(t, c)という値のストリームを表しています。他にもユーザがマウスをクリックするごとに発生するeventや、ボンネットの温度が5度あがるごとに発生するevent、ブランコの速度が0になるたびに発生するevent、あるいは株式市場で取引の開始・終了を知らせるeventなど、eventもまたありふれています。

FRPは、これらのbehaviorとeventをうまく組み合わせて計算する仕組みを提供します。

たとえばブランコが何度揺れたかを記録したかったら、速度が0になるeventを数えればよいですし、ユーザがクリックしたときの座標を取得するなら、マウス座標を表すbehaviorに対して、ユーザのクリックを表すeventでサンプリングします。あるいはマウスが移動した距離を測りたかったら、マウスの移動速度を表すbehaviorを積分すればよいでしょう。

FRPの一番の功績は、これら普通に実装すると関数的には書きにくいreactiveなプログラミングを、関数的に書き下すことができるようになるという点です。そんなことを聞くとワクワクしてきませんか。

参考情報

Haskell以外のFRPライブラリというと下記のものが挙げられます。

HaskellにおけるFRP

Haskell界では10年以上も前からFRPに関する研究が続けられているようです。ライブラリ自体はいくつもあるのですが、出所や特徴から大体こんな感じで分類できそうです。

  • 古典的FRP
    • The Haskell School of Expressionで取り上げられているようなFRP。基本pull式。
    • Reactive
      • Reactive - HaskellWiki
      • PanとかFranとか、長らくFRPについて研究しているConal Elliottが作った最新作。
    • Elerea
  • Arrow化された(Arrowised)FRP
    • 古典的FRPが抱える時間・メモリリークをSignal Functionで解決するもの。たぶんpull式。
    • Yampa
      • 404 Not Found
      • Yale Haskell Groupで作られたもの。SOEを書いたPaul Hudakの居るところ。Arrowised FRPで一番メジャーなもの。
  • push式FRP
  • その他
    • Buster
      • Buster | Hieroglyphics
      • Hieroglyphという2Dグラフィクスライブラリを作っている人が作ったFRP。よく知らないけど、grapefruitとbusterはI/Oの使い方が似てるという話をどこかで見た。

歴史があって応用アプリが多いのはYampaで、pull/push両陣営の最新ライブラリはReactiveとGrapefruitではないかと思っています。

参考情報

まとめ

FRPについて、behaviorとeventという重要な概念とHaskellでの代表的なライブラリを簡単に紹介しました。FRPの良くない点は、わかりやすいドキュメントが少ない点だと思っています。SOEはFRPを理解する上でとても役に立ちそうな本ですが、実際に既存のライブラリを使おうと思うとよくわからなくなってしまいます。

次は古典的FRPの最新版といえるReactiveについてサンプルコードを使って紹介して、いかにI/Oとロジックを分離するか、またそれによって得られる利点について書ければいいなと思います。

最後に間違いの指摘や有益な情報はいつでも大歓迎です。気がついたら是非お知らせください。