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

bitterharvest’s diary

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

ボールの衝突をFunctional Reactive Programmingで表現する(5)

6.Reactive Functional Programmingの神髄

以前の記事で、ニュートン力学に従って落下する物体が地面で跳ね返る様子を記述したが、その時のプログラムは次のようになっていた。

rec a <- pure aInit -< undefined
    v <- integralWith' correct vInit -< (a,c)
    (p, c) <- integralWith" clamp pInit -< v

と書いた。

この中で使われているintegralWith', integralWith"は次のようになっていた。

integralWith' ::
    (HasTime t s)
    => (V2 Bool -> V2 Double -> V2 Double)
    -> V2 Double
    -> Wire s e m (V2 Double, V2 Bool) (V2 Double)
integralWith' correct' = loop
    where
    loop x' =
        mkPure $ \ds (dx, w) ->
            let dt = realToFrac (dtime ds)
                x  = correct' w (x' + dt*dx)
            in x' `seq` (Right x', loop x)

integralWith" ::
    (HasTime t s)
    => (V2 Double -> (V2 Double, V2 Bool))
    -> V2 Double
    -> Wire s e m (V2 Double) (V2 Double, V2 Bool)
integralWith" correct" = loop
  where
    loop y' =
        mkPure $ \ds dy ->
            let dt = realToFrac (dtime ds)
                (y,c)  = correct" (y' + dt*dy)
            in y' `seq` (Right (y', c), loop y)

上記のプログラムは必要があるときは追って欲しいが、跳ね返るボールのプログラムは次のように動く。

現時点\(t\)での加速度\(a\)、速度\(v_t\)、位置\(p_t\)が出力される(プログラムがスタートした時の最初の出力はaInit,vInit,pInitである)。速度、位置の出力は、細かく説明すると、それぞれ、integralWith',integralWith"から出力され、それぞれは、Right x', Right (y' c)である(それぞれの関数で、loop x', loop y'の中を計算し、最後のinのところにあるRightから出力される)。

位置のほうからは、Right (y' c)からわかるように、現在の位置y'ばかりではなく、cというものも併せて出力される。cは、\(dt\)時間が経過した次の時点までに衝突が生じるかどうかを示すブール値である。衝突が生じる場合には、次の速度も変える必要があるため、速度の計算をする関数にcが与えられる。

もし、次の計算時間(現時点から\(dt\)経過した後)がくるまでに地面に衝突した場合には、\(dt\)経過した後の出力は、位置と速度は飛び跳ねた後のものでなければならない。これらの値は、loop x', loop y'の中のx = correct' w (x' + dt*dx)、(y,c) = correct" (y' + dt*dy)で計算され、次の計算時間になった時に、loop x, loop yを計算する。従って、loop x', loop y'の中では、Right x', Right (y', c)による出力が終わった後、次のloop x, loop yをすべく待機している。この時、それぞれの正しい位置と速度の値がどのように設定されているのかを次に見ることとする。

位置のほうは、y=correct" (y' + dt*dy)から、直接(既知のy', dt, dyから計算できるため)、求めることができる。しかし、速度の方では、x = correct' w (x' + dt*dx)を計算するとき、wを必要とする。このwには、加速度vとブール値cを必要とする。加速度、速度、位置を求める関数はrecによって並列に動いているが、速度の方の関数は、位置の方の関数の出力cを必要とするので、位置の方の関数がこの値を出力するまでは、correct' w (x' + dt*dx)の計算を待つ。これがいわゆる遅延処理といわれるものである。

従って、位置の方の計算が終了した段階で、correct' w (x' + dt*dx)が計算され、次の計算時間でのx,yを準備することができ、\(dt\)だけ経過した後のloop x, loop yのための計算の用意ができた状態になる。

これにより、時間\(t\)から\(dt\)だけ経過する間に衝突が生じた場合にも、位置と速度は跳ね返り後の正しい値になり、その値を用いて、次の時間での処理が行われる。

プログラムがどのように進行していくかを図で示す。
f:id:bitterharvest:20151003165527p:plain

少し、説明が長くなったが、Netwireでは、このようにして、速度、位置を正しく求めるようにしている。このように、あるところ(この場合では位置)での変化が、他のところでの(この場では速度)変化として反映されるのが(Functional) Reactive Programmingの意図するところである。