読者です 読者をやめる 読者になる 読者になる

bitterharvest’s diary

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

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

11.アロー記法

wireは、入力aを受けて、出力bを出すことで、その振る舞いを表しているが、前の記事では、入力aの存在が分かりにくかったので、wireの本質をとらえにくかったかもしれない。

そこで、ここでは、前の記事で用いたプログラムを等価なアロー記法で表すことにする。
アロー記法の基本的な表現は、b <- wire -< aである。ここで、aがwireへの入力、bがwireからの出力である。

なお、以下のプルグラムでは、アロー記法とNetwireを用いるために、プログラムの最初に次のインポート文などを書いておく。

{-# LANGUAGE Arrows #-}

import Prelude hiding ((.)) -- To use (.) in the scope of Categories instead
import Control.Wire
import FRP.Netwire

11.1 簡単な振舞い

定数23は出力するWire型の値は次のようになる。

ex1 :: Monad m => Wire s () m a Int
ex1 = pure 23
main1 :: IO ()
main1 = testWire  (pure ()) ex1

これを、アロー記法で記述すると以下のようになる。

ex1 :: Monad m => Wire s () m a Int
ex1 = proc _ -> do
        b <- pure 23 -< ()
        returnA -< b
main1 :: IO ()
main1 = testWire clockSession_ ex1

23にかわって、Double型の値を外部から与え、これを出力するWire型の値を作成する。

ex2 :: Monad m => Double -> Wire s () m a Double
ex2 x = pure x
main2 :: Double -> IO ()
main2 x = testWire (pure ()) $ ex2 x

これを、アロー記法で記述すると以下のようになる。

ex2 :: Monad m => Double -> Wire s () m a Double
ex2 x = proc x -> do
          b <- pure x -< ()
          returnA -< b
main2 :: Double -> IO ()
main2 x = testWire clockSession_ $ ex2 x

次の例は、外部から値を得て、それを利用した計算結果を出力するWire型の値である。

ex3 :: Monad m => Double -> Wire s () m a Double
ex3 x = pure $ x * x - 2
main3 :: Double -> IO ()
main3 x = testWire (pure ()) $ ex3 x

これを、アロー記法で記述すると以下のようになる。

ex3 :: Monad m => Double -> Wire s () m a Double
ex3 x = proc x -> do
          b <- pure $ x * x - 2 -< ()
          returnA -< b
main3 :: Double -> IO ()
main3 x = testWire clockSession_ $ ex3 x

次は、外部から文字列を与え、それを出力するWire型の値である。

ex4 :: Monad m => String -> Wire s () m a String
ex4 x = pure x
main4 :: String -> IO ()
main4 x = testWire (pure ()) $ ex4 x

これを、アロー記法で記述すると以下のようになる。

ex4 :: Monad m => String -> Wire s () m a String
ex4 x = proc x -> do
          b <- pure x -< ()
          returnA -< b
main4 :: String -> IO ()
main4 x = testWire clockSession_ $ ex4 x

11.2 時間

Wire型の値は、時計を有することが可能であるが、それ自身の時計の現在の時刻を出力するWire型の値は次のようになる。

ex5 :: (Monad m, HasTime t s) => Wire s () m a t
ex5 = time
main5 :: IO ()
main5 = testWire clockSession_ ex5

これを、アロー記法で記述すると以下のようになる。

ex5 :: (Monad m, HasTime t s) => Wire s () m a t
ex5 = proc _ -> do
        b <- time -< ()
        returnA -< b
main5 :: IO ()
main5 = testWire clockSession_ ex5

Wire型の値が4倍の速さで120からカウントダウンを行うときは、次のようになる。

ex6 :: (Monad m, HasTime t s) => Wire s () m a t
ex6 = liftA2 (\speed countdown -> countdown - 4*speed) time (pure 120)
main6 :: IO ()
main6 = testWire clockSession_ ex6

これを、アロー記法で記述すると以下のようになる。

ex6 :: (Monad m, HasTime t s) => Wire s () m a t
ex6 = proc _ -> do
        b <- liftA2 (\speed countdown -> countdown - 4*speed) time (pure 120) -< ()
        returnA -< b
main6 :: IO ()
main6 = testWire clockSession_ ex6

11.3 位置、速度、加速度

等速運動を行うWire型の値は次のようになる。

ex7 :: (Monad m, HasTime t s) => Wire s () m a Double
ex7 = integral 10 . 8
main7 :: IO ()
main7 = testWire clockSession_ ex7

これを、アロー記法で記述すると以下のようになる。

ex7 :: (Monad m, HasTime t s) => Wire s () m a Double
ex7 = proc _ -> do
        b <- integral 10 -< 8
        returnA -< b
main7 :: IO ()
main7 = testWire clockSession_ ex7

100メートルの高さから落下するWire型の値は次のようになる。

ex8 :: (Monad m, HasTime t s, Fractional t) => Wire s () m a t
ex8 = integral (100.0) . (10 + (-9.8) *  time)
main8 :: IO ()
main8 = testWire clockSession_ ex8

これを、アロー記法で記述すると以下のようになる。

ex8 :: (Monad m, HasTime t s, Fractional t) => Wire s () m a t
ex8 = proc _ -> do
        t <- time -< ()
        b <- integral 100.0 -< 10 + (-9.8) *  t
        returnA -< b
main8 :: IO ()
main8 = testWire clockSession_ ex8

11.4 間隔

5秒間"Yes, we can do."と出力するWire型の値は次のようになる。

ex9 :: (Monad m, HasTime t s) => Wire s () m a String
ex9 = for 5 . (pure "Yes, we can do.")
main9 :: IO ()
main9 = testWire clockSession_ ex9

これを、アロー記法で記述すると以下のようになる。

ex9 :: (Monad m, HasTime t s) => Wire s () m a String
ex9 = proc _ -> do
        b <- for 5 -< "Yes, we can do."
        returnA -< b
main9 :: IO ()
main9 = testWire clockSession_ ex9

文字列ではなく時間の場合には次のようになる。

ex9' :: (Monad m, HasTime t s) => Wire s () m a t
ex9' = for 5 . time
main9' :: IO ()
main9' = testWire clockSession_ ex9'

これを、アロー記法で記述すると以下のようになる。

ex9' :: (Monad m, HasTime t s) => Wire s () m a t
ex9' = proc _ -> do
         t <- time -< ()
         b <- for 5 -< t
         returnA -< b
main9' :: IO ()
main9' = testWire clockSession_ ex9'

5秒経過した後から出力する場合は次のようになる。

ex9'' :: (Monad m, HasTime t s) => Wire s () m a t
ex9'' = after 5 . time
main9'' :: IO ()
main9'' = testWire clockSession_ ex9''

これを、アロー記法で記述すると以下のようになる。

ex9'' :: (Monad m, HasTime t s) => Wire s () m a t
ex9'' = proc _ -> do
          t <- time -< ()
          b <- after 5 -< t
          returnA -< b
main9'' :: IO ()
main9'' = testWire clockSession_ ex9''

11.5 イベント

3秒経過したら"3 seconds."を出力するWire型の値は次のようになる。

ex11 :: (Monad m, HasTime t s) => Wire s () m a String
ex11 = asSoonAs . at 3 . (pure "3 seconds.")
main11 :: IO ()
main11 = testWire clockSession_ ex11

これを、アロー記法で記述すると以下のようになる。

ex11 :: (Monad m, HasTime t s) => Wire s () m a String
ex11 = proc _ -> do
         a <- at 3 -< "3 seconds." 
         b <- asSoonAs -< a
         returnA -< b
main11 :: IO ()
main11 = testWire clockSession_ ex11

文字列に代わって時間を出力させると次のようになる。

ex11' :: (Monad m, HasTime t s) => Wire s () m a t
ex11' = asSoonAs . at 3 . time
main11' :: IO ()
main11' = testWire clockSession_ ex11'

これを、アロー記法で記述すると以下のようになる。

ex11' :: (Monad m, HasTime t s) => Wire s () m a t
ex11' = proc _ -> do
          t <- time -< ()
          a <- at 3 -< t 
          b <- asSoonAs -< a
          returnA -< b
main11' :: IO ()
main11' = testWire clockSession_ ex11'

2秒経過した後に"Yes, we can do."と出力し、4秒経過した後に"No, you cannot do."と出力するWire型の値は次のようになる。

ex12 :: (Monad m, HasTime t s) => Wire s () m a String
ex12 = asSoonAs . (at 2 . (pure "Yes, we can do.") <& at 4 . (pure "No, you cannot do."))
main12 :: IO ()
main12 = testWire clockSession_ ex12

これを、アロー記法で記述すると以下のようになる。

ex12 :: (Monad m, HasTime t s) => Wire s () m a String
ex12 = proc _ -> do
         a <- (<&) proc1 proc2 -< ()
         b <- asSoonAs -< a
         returnA -< b
         where
           proc1 = proc _ -> do
             b <- at 2 -< "Yes, we can do." 
             returnA -< b
           proc2 = proc _ -> do
             b <- at 4 -< "No, you cannot do." 
             returnA -< b
main12 :: IO ()
main12 = testWire clockSession_ ex12

時間に代えると次のようになる。

ex12' :: (Monad m, HasTime t s) => Wire s () m a t
ex12' = asSoonAs . (at 2  <& at 4) . time
main12' :: IO ()
main12' = testWire clockSession_ ex12'

これを、アロー記法で記述すると以下のようになる。

ex12' :: (Monad m, HasTime t s) => Wire s () m a t
ex12' = proc _ -> do
          t <- time -< ()
          a <- (<&) (at 2) (at 4) -< t
          b <- asSoonAs -< a
          returnA -< b
main12' :: IO ()
main12' = testWire clockSession_ ex12'

11.6 スイッチ

5秒経過するまでは"Yes, we can do."を出力し、その後は"No, you cannot do."を出力するWire型の値は次のようになる。

ex13 :: (Monad m, HasTime t s) => Wire s () m a String
ex13 = for 5 . (pure "Yes, we can do.") --> (pure "No, you cannot do.")
main13 :: IO ()
main13 = testWire clockSession_ ex13

これを、アロー記法で記述すると以下のようになる。

ex13 :: (Monad m, HasTime t s) => Wire s () m a String
ex13 = proc _ -> do
         a <- proc1 -< ()
         b <- (-->) (for 5) proc2 -< a
         returnA -< b
         where
           proc1 = proc _ -> do
             b <- pure "Yes, we can do." -< ()
             returnA -< b
           proc2 = proc _ -> do
             b <- pure "No, you cannot do." -< ()
             returnA -< b
main13 :: IO ()
main13 = testWire clockSession_ ex13

文字列を時間に代えると次のようになる。

ex13' :: (Monad m, HasTime t s) => Wire s () m a t
ex13' = for 5 . time --> time
main13' :: IO ()
main13' = testWire clockSession_ ex13'

これを、アロー記法で記述すると以下のようになる。

ex13' :: (Monad m, HasTime t s) => Wire s () m a t
ex13' = proc _ -> do
          t <- time -< ()
          b <- (-->) (for 5) time -< t
          returnA -< b
main13' :: IO ()
main13' = testWire clockSession_ ex13'

イルミネーションのようにしたWire型の値は次のようになる。

ex14 :: (Monad m, HasTime t s, Fractional t) => Wire s () m a String
ex14 =
    for 2 . (pure "Once upon a time...") -->
    for 3 . (pure "... games were completely imperative...") -->
    for 2 . (pure "... but then...") -->
    for 10 . ((pure "Netwire 5! ") <> anim) -->
    ex14

  where
    anim =
        holdFor 0.5 . periodic 1 . (pure "Hoo...") <|>
        (pure "...ray!")
main14 :: IO ()
main14 = testWire clockSession_ ex14

これを、アロー記法で記述すると以下のようになる。

ex14 :: (Monad m, HasTime t s, Fractional t) => Wire s () m a String
ex14 = proc _ -> do
         a <- pure "Once upon a time..." -< ()
         b <- (-->) (for 2) proc1 -< a
         returnA -< b
         where
           proc1 = proc _ -> do
             a <- pure "... games were completely imperative..." -< ()
             b <- (-->) (for 3) proc2 -< a
             returnA -< b
           proc2 = proc _ -> do
             a <- pure "... but then..." -< ()
             b <- (-->) (for 2) proc3 -< a
             returnA -< b
           proc3 = proc _ -> do
             a <- (<>) (pure "... but then...") anim -< ()
             b <- (-->) (for 10) ex14 -< a
             returnA -< b
           anim =  proc _ -> do
                     b <- holdFor 0.5 . periodic 1 . (pure "Hoo...") <|> (pure "...ray!") -< ()
                     returnA -< bmain14 :: IO ()
main14 :: IO ()
main14 = testWire clockSession_ ex14

アロー記法によってWire型の本質的な部分が明らかになったと思う。時間からは、Netwireを用いてのゲームの作成に移る。