言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

SOE 15.3 章 The Implementation

いよいよ反応プログラミングの核心に入って行きます。前回までの復習。

  • プログラムは振る舞い (Behavior) を組み合わせて作る。
  • Behavior は動作 (UserAction) と時間 (Time) の組を受け取りある値を返す関数
  • Behavior の引数も返り値もストリーム
  • Behavior に イベント(Event) を設定した物は Behavior である。
  • Event は Behavior の返り値が Maybe 型の物。
Behavior と Event を組み合わせて新たな Behavior を作る untilB の実装で
す。本の記述は難しいので、(自分にとって)分かりやすいように大幅に改造し
た untilB1 として考えます。

> module MyReactimate3 where
> import Fal  -- FAL モジュール
> import MyReactimate
> import SOEGraphics (withColor) -- 低レベル画像モジュール
> import Memo1

同じ型が何度も登場するので別名をつけます。In はユーザの操作と時間を組
み合わせたシステム全体の環境を表す事にします。と言うと大げさですが、単
なる入力ストリームの事です。[a] は普通は「型 a のリスト」ですが、「型 
a の出力ストリーム」を強調するのに Out a としてみます。パターンマッチ
ング x:xs を使ったときに、x が現在の値で、xs が残りのストリームです。
実装を見ると、実は In は [(Maybe UserAction,Time)] の方が良かったんじゃ
ないかという気がします。

> type In = ([Maybe UserAction],[Time])
> type Out a = [a]

In と Out を使って表した Behavior と Event の型は以下のようになります。

- newtype Behavior a = Behavior (In -> Out a)
-- 入力ストリームから a の値をひたすら出力する装置
- newtype Event a = Event (In -> Out (Maybe a))
-- 入力ストリームから a の値をたまに出力する装置

untilB1 は Behavior と Event を合成して Behavior を作ります。本では 
memo1 によって最適化をしていますが、意味が分からないので端折ります。

> untilB1 :: Behavior a -> Event (Behavior a) -> Behavior a
> Behavior fb `untilB1` Event fe = Behavior (inner fb fe)

untilB の実装です。Behavior と Event の中身から、新しい Behavior の中
身を返します。返り値は入力ストリームから出力ストリームを返す関数です。
関数の中では loop が呼ばれ、出力ストリームが返されます。

> inner :: (In -> Out a) -> (In -> Out (Maybe (Behavior a))) -> (In -> Out a)
> inner fb fe = (\uts -> loop uts (fb uts) (fe uts))

実際に合成して出力ストリームを返します。出力ストリームの最初の値 b は、
イベントが無かった場合の値がそのまま使われます。これは再帰を正しく進め
るためです。ためしに b : を削除してみるとちゃんとデッドロックを検出し
ます。~ は遅延評価を強制するらしいですが、よくわからないです。

- 引数1 : 今の入力ストリーム
- 引数2 : イベントが無かった場合の今の出力ストリーム
- 引数3 : イベントがあった場合の今の出力ストリーム
- 返値 : 結果の出力ストリーム

> loop :: In -> Out a -> Out (Maybe (Behavior a)) -> Out a
> loop ((_:us),(_:ts)) (b:bs) ~(e:es) = b : nextOut e (us, ts) bs es

次の値を決めます。イベントが無ければそのまま次に入力ストリームを進めま
す。あれば見つかった Behavior に取って代わります。

- 引数1 : イベントがあった時の今の値
- 引数2 : 次の入力ストリーム
- 引数3 : イベントが無かった場合の次の出力ストリーム
- 引数4 : イベントがあった場合の次の出力ストリーム
- 返値 : 結果の出力ストリーム

> nextOut :: Maybe (Behavior a) -> In -> Out a -> Out (Maybe (Behavior a)) -> Out a
> nextOut Nothing              uts bs es = loop uts bs es
> nextOut (Just (Behavior fb)) uts _  _  = fb uts

さて、同じように動くでしょうか?

> showClickColor8 =
>      let c = red `untilB1` (lbp ->> yellow)
>          g = lift2 withColor c nowGraphic
>      in reactimate "time" g

そんなにたいした事をやってるわけじゃないのにこの難しさはなんだろう。。。たぶん。イベントや出力なんかの具体的の処理の前に動作の合成なんていう抽象的な物の説明をやってるからじゃないかな。。。というわけで、次回はイベント実装の部分を読む予定です。