FRPNowでFizzBuzzなど

ICFP2015に[論文](http://www.reddit.com/r/haskell/comments/3ai7hl/principled_practical_frp_forget_the_past_change/)が出るのだそうで、局所的に話題になっている[FRPNow](https://hackage.haskell.org/package/frpnow)というライブラリ。
ちょっと触ってみたので、感触などレポート。

とりあえずFizzBuzz
----------

``` haskell
{-# LANGUAGE MultiWayIf #-}

module Main where

import Control.FRPNow
import Data.Functor ((<$), (<$>))
import Control.Monad (join)     

streaming ::
    (EvStream a -> Behavior (EvStream b, Event ())) ->
    IO a ->
    ([b] -> IO ()) ->
    IO ()
streaming b ioA ioB = runNowMaster $
  do
    (esA, cbA) <- callbackStream
    (esB, evE) <- sampleNow (b esA)
    loop cbA esB
    return evE

  where
    loop cbA esB =
      do
        eys <- sampleNow (nextAll esB)
        _ <- async (ioA >>= cbA)
        eev <- planNow $ async . ioB <$> eys
        planNow $ loop cbA esB <$ join eev
        return ()


mainB ::
    EvStream () -> Behavior (EvStream String, Event ())
mainB inEs = 
  do
    countUp <- foldEs (+) 0 (1 <$ inEs)
    end <- when ((>100) <$> countUp)
    return (toFizzBuzz countUp `snapshots` inEs, end)
  where
    toFizzBuzz :: Behavior Int -> Behavior String
    toFizzBuzz bX =
      do
        x <- bX
        let fizz = x `mod` 3 == 0
            buzz = x `mod` 5 == 0
        return $ if
            | fizz && buzz -> "FizzBuzz"
            | fizz -> "Fizz"
            | buzz -> "Buzz"
            | otherwise -> show x
    
main = streaming mainB (return ()) (mapM_ putStrLn)
```

streaming関数は汎用的に書いたドライバで、ロジック部分はmainBに集約されている。ロジックの表現としては、それなりに良い感じなのではないか、と思う。

FRPの基礎概念
--------

ここでFRPの基礎をなす概念、EventとBehaviorについて簡単に触れる。

<dl>
  <dt>Behavior</dt>
  <dd>時間に伴って変化する値。Behavior aはすなわちTime -> aの意。
</dd>
  <dt>Event</dt>
  <dd>時間の中の一点にある値。Event aはだいたい(Time, a)。
</dd>
</dl>

なお、このEventの定義はSodiumやYampaといった既存のFRPと少し異なる。SodiumやYampaのEventは時系列上に何回でも発生する事ができる(式で表すと`Event a 〜 [(Time, a)]`みたいな感じ)のに対し、FRPNowのEventは一度きりしか起きない。どちらかというとこれは、他の言語の[promise/future](http://d.hatena.ne.jp/debug-ito/20130414/1365923218)に近いものであり、実際そういう使い方ができる事を後で示す。

FRPNowでは、普通のFRPの`Event a`に相当するものとして、`EvStream a`という型が別途定義されている。

mainBの定義
--------
それを踏まえるとmainBは、時間に伴って流れてくる`()`の列を受け取り、Stringの列と一つのイベントのペアを返す関数である事が分かる……と言いたい所だが、戻り値がBehaviorに包まれているのは一体何であろうか。

実を言うと、概念的には戻り値は`(EvStream String, Event ())`でも問題ないのだが、foldEsやwhenという関数がBehaviorに包まれた値を返してくるので、mainBもそれに伴ってBehaviorに包んだ値を返さざるを得ないのである。

なぜ余計なBehaviorで包むのかという、この点こそFRPNowの元論文の肝なので、興味のある方は直接読んで頂くのをお薦めする。だが、実プログラミング上でこの点を気にする必要はあまりない。Behaviorはモナドになっているので、returnや>>=を使えば簡単にBehavior型の値が作れるし、実行段階(後述するNowモナドの中)においてはいつでも、sampleNowという関数でBehaviorを剥がす事が可能だからだ。

さて、mainBの内容だが、()の列を受け取ると、まずはfoldEs関数を使ってcountUpというBehaviorを構築している。

```hs
foldEs :: (a -> b -> a) -> a -> EvStream b -> Behavior (Behavior a) 
```

これは初期値aに対し、ストリームの値を次々に二項演算で適用していったBehaviorを示す。
開始時点では0、それから()が一つ来る度に0+1、0+1+1、0+1+1+1という具合に数字が増えていくのである。

あとはロジックに従ってcountUpをFizzBuzz文字列に直し、それをsnapshotsでストリームに直したものを第一の戻り値としている。

第二の戻り値は停止条件を表す。

``` hs
end <- when ((>100) <$> countUp)
```

という、いかにもそのままの定義だが、「countUpが100を超える瞬間」を第二の戻り値としている。

streamingの定義
--------------
streaming関数は、駆動すべきストリームb、入力値を受け取るためのアクションioA、出力値を処理するためのアクションioBを受け取り、bの第二戻り値が示す終端イベントが発生するまでの間、延々と処理を繰り返す関数である。

ioAに、たとえばgetLineでも渡してやる事によって、インタラクティブなCUIアプリケーションも作れるようにしたのだが、FizzBuzzは入力を取らないので、main関数では単に(return ())を渡し、延々と()を流すだけの入力ストリームとしている。出力にはputStrLnで画面に表示する処理を渡す。

そして、streaming関数の処理のキーとなるのはFRPNowで新星の如く現れた新概念、Nowモナドである。

NowモナドはFRPNowにおいて唯一の、外界と繋がる事ができるモナドであるが、Nowモナドのチェーンに含まれるものは全て同時に行われたとみなす点がIOモナドと異なる。このために、時間のかかるIOアクションの実行にはasyncを用いる。

``` hs
async :: IO a -> Now (Event a) 
```

ここで思い出して頂きたいのは、「Eventはpromise/futureに似ている」という話である。まさしく、asyncの実行結果は他言語でいうところのfutureであるEvent型に包まれて返ってくる。

となるとここに、futureに続く処理を登録するような何かがあるはずであるが、これは以下の関数を使用する。

``` hs
planNow :: Event (Now a) -> Now (Event a) 
```

Eventに包んだアクションを「予約」して、その戻り値をまたEventとして受け取る訳である。

streamingのloop関数では、まず出力ストリームesBの「次の値」nextAllの取得を予約したあと、ioA>>=cbAをasyncに実行している。cbAは値を入力ストリームに渡すためのコールバック関数であり、これによってシグナルネットワークが稼働して出力ストリームesBから値が出てくる。これらの非同期処理の結果はeysに入ってくるので、さらに非同期アクションioBの実行を予約。それが済んだらloopを再度実行、以下繰り返す。

「次の値」nextAllは、もちろん実行のその時々によって変化するから、その戻り値は`Behavior (Event [a])`という風に、時間変化するイベントになっている。Nowモナドの中では、Behaviorの「現時点での値」を`sampleNow`で取得する事ができる。loopの呼び出し時刻は、planNowによって少しずつ未来に遅延されていくから、sampleNowで取得できる値も少しずつ未来のものになる。これによって、ストリームの値を時間を時系列順に列挙できる、という仕組みである。

他のFRPとの比較
----------
先ほど、色々なものがわざわざBehavior型に包まれて返ってくるのは気にする必要はない、というくだりがあったが、実はこの点は他のFRPライブラリと比べてちょっとしたアドバンテージになっている。

例えば、SodiumでfoldEsに相当するaccum関数の定義は以下の通り。

``` hs
accum :: a -> Event (a -> a) -> Reactive (Behavior a) 
```

戻り値はReactive型（一般的な論文ではgeneratorと称されるもの）に包まれているが、これはFRPNowのNow型に相当し、IOアクションの実行が可能である（但し非同期実行の機能はなく、IOアクションは同期的に実行される）。こういう「副作用はないが、直近の入力値以上の何かに依存する」形のシグナル関数を、わざわざIOアクションが可能なモナドに包んで返すというのは、純粋性という観点からちょっと問題に感じる。

そこで、こういったものをgeneratorという概念を使わず、かつ既存のBehavior型で表現できるというのは、なかなか良いアイデアなのではないだろうか。最初に挙げた論文のredditコメントでも、その点について少し議論が行われている（同じ物が違う文脈で出てきて分かりづらいよ、というツッコミもあるが）。

まとめ
----------

* FRPNowは既存のFRPと同様にBehaviorやEventの概念を扱えるが、Behavior/Event層と生IO層の間にpromise/futureのような層があるのが面白い
* いろいろなものがBehaviorに包まれて返ってくるが、慣れればどうという事はないしむしろ便利