bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

Netwireでゲームを作成する(4)

6.関数型リアクティブ・プログラミング

関数型リアクティブ・プログラミングを特徴づけるのが、スイッチである。これに関してはYampaの記事でいくつか取り上げた。ここでは、最も簡単なスイッチを用いて、Netwireではどのように実現するのかを説明する。

スイッチの中で最も簡単なものは下図である。
f:id:bitterharvest:20150615061046p:plain
このスイッチは、イベントがなければそのまま入力dをそのまま返すが、イベントを受けた場合には入力bに関数を適応し、cを返す。

このスイッチをあるキーが押し下げられた時、四角い箱を動かす場合に適応すると次のようになる。
f:id:bitterharvest:20150615061747p:plain
即ち、
(1)キーボードが押し下げられた時、AまたはDならば、boxを左あるいは右に動かす。それ以外の時はそのままである。
(2)押し下げられなければ、boxはそのままidlingである。

これをプログラムで表すことを考える。初期状態はアイドリングしているとする。

idling :: Behavior t ([Input],Box) Box
idling = Behavior (const snd)

上記の関数idlingは、アイドリングという振舞いを表現している。入力として、[Input]とBoxの二つを受ける。[Input]は押し下げられたキーの列が入ってくる。Boxは現在の四角い箱の状態を表している。この関数は、何もしないので、2番目の入力boxを出力する。

キーボードが押し下げられたことによる四角い箱の移動は、キーボードの状態と圏と四角い箱の状態の件を定義し、前者の圏から後者の圏に関手を張ることで、示すことができる。
f:id:bitterharvest:20150615063049p:plain

上記をプログラミングすると次のようになる。

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) }

これで、振舞いの定義は完成した。次の記事では、圏として使えるようにするため、圏にかかわる様々な型クラスのインスタンスとして、振舞いを定義する。