6.関数型リアクティブ・プログラミング
関数型リアクティブ・プログラミングを特徴づけるのが、スイッチである。これに関してはYampaの記事でいくつか取り上げた。ここでは、最も簡単なスイッチを用いて、Netwireではどのように実現するのかを説明する。
スイッチの中で最も簡単なものは下図である。
このスイッチは、イベントがなければそのまま入力dをそのまま返すが、イベントを受けた場合には入力bに関数を適応し、cを返す。
このスイッチをあるキーが押し下げられた時、四角い箱を動かす場合に適応すると次のようになる。
即ち、
(1)キーボードが押し下げられた時、AまたはDならば、boxを左あるいは右に動かす。それ以外の時はそのままである。
(2)押し下げられなければ、boxはそのままidlingである。
これをプログラムで表すことを考える。初期状態はアイドリングしているとする。
idling :: Behavior t ([Input],Box) Box idling = Behavior (const snd)
上記の関数idlingは、アイドリングという振舞いを表現している。入力として、[Input]とBoxの二つを受ける。[Input]は押し下げられたキーの列が入ってくる。Boxは現在の四角い箱の状態を表している。この関数は、何もしないので、2番目の入力boxを出力する。
キーボードが押し下げられたことによる四角い箱の移動は、キーボードの状態と圏と四角い箱の状態の件を定義し、前者の圏から後者の圏に関手を張ることで、示すことができる。
上記をプログラミングすると次のようになる。
inputToBehavior i = case i of A -> Behavior $ \_ (_,box) -> box & boxPosition . _x -~ 0.1 D -> Behavior $ \_ (_,box) -> box & boxPosition . _x +~ 0.1 _ -> Behavior $ \_ (_,box) -> box
なお、上記のプログラムは、次のプログラムと同じである。
inputToBehavior i = case i of A -> Behavior {stepBehavior = \_ (_,box) -> box & boxPosition . _x -~ 0.1} D -> Behavior {stepBehavior = \_ (_,box) -> box & boxPosition . _x +~ 0.1} _ -> Behavior {stepBehavior = \_ (_,box) -> box}
stepBehaviorという関数名を陽に表すことを止めて、無名関数化した。
ここまでのプルグラムをまとめると、以下のようになる。EventがFunctorのインスタンスになっていることに注意してほしい。
{-# LANGUAGE TemplateHaskell #-} import Linear.V3 import Control.Lens data Input = A | D | Quit deriving (Eq,Read,Show) newtype Box = Box { _boxPosition :: V3 Float } deriving (Eq,Read,Show) makeLenses ''Box newtype Behavior t a b = Behavior { stepBehavior :: t -> a -> b } newtype Event t a = Event { getEvent :: (t,a) } idling :: Behavior t ([Input],Box) Box idling = Behavior (const snd) instance Functor (Event t) where fmap f (Event e) = Event (fmap f e) inputToBehavior i = case i of A -> Behavior $ \_ (_,box) -> box & boxPosition . _x -~ 0.1 D -> Behavior $ \_ (_,box) -> box & boxPosition . _x +~ 0.1 _ -> Behavior $ \_ (_,box) -> box
これによりスイッチの部分が表せるようになった。
7.状態機械に
四角い箱は、状態として座標を持っている。従って、次の動作に移るときに、状態を知らなければいけないので、新しい状態を保持する機能を有しなけらばならない。
オートマトンでは、状態を保持するために、出力のなかに次の状態を含めている。
newtype Auto a b = Auto { runAuto :: a -> (b,Auto a b) }
これに習って、振舞いの定義を以下のように変更する。
newtype Behavior t a b = Behavior { stepBehavior :: t -> a -> (b,Behavior t a b) }
これで、振舞いの定義は完成した。次の記事では、圏として使えるようにするため、圏にかかわる様々な型クラスのインスタンスとして、振舞いを定義する。