yampaのpSwitch

yampaについて調べていると単純なswitchについての説明はいくつか見つかるのだが、pSwitchについてはなかなか見つからなかったのでわかったことをメモしておく。
こちらのブログとhaskanoidのソースコードを参考にした。
http://bitterharvest.hatenablog.com/entry/2014/11/08/062127

基本的なswitch

基本的なswitchについては上記ブログの別のページにわかりやすい説明がある
http://bitterharvest.hatenablog.com/entry/2014/10/26/093636

一応switchについても書いておくと、型は以下のようになっている

switch :: SF a (b, Event c) -> (c -> SF a b) -> SF a b

1つ目の信号関数出力として、信号関数そのものの出力だけでなくswitchすべき条件下で発生するEventを定義する。2つ目の引数ではそこで発生したEventの中身に従ってswitchさせる信号関数を返す。

上記ブログで書かれているコードを例にすると

bouncingBall :: Pos -> Vel -> SF () (Pos, Vel)
bouncingBall y0 v0 = switch (bb y0 v0) (\ (pos, vel) -> bouncingBall pos ((-vel) * 0.6))
  where bb y0' v0' = proc input -> do
                      (pos, vel) <- fallingBall y0' v0' -< input
                      event <- edge -< pos <= 0
                      returnA -< ((pos, vel), event `tag` (pos, vel))

fallingBallというのは自由落下を表現する別の信号関数である。switchの1つ目の引数として、そのfallingBallの出力(=自由落下時の各時刻での位置と速度)とswitchすべき条件(pos <= 0)下で発生するイベント(中身はそのときの位置と速度)を定義している。
2つ目の引数ではイベント発生時の速度を参照することで、初期速度を負の値にしてbouncingBallを再度呼んでいる。Event発生以後は常にこちらの処理がされるようになる。そして、再度pos<=0の条件を満たしたときにはEventが発生し、再びbouncingBallが呼ばれる。
以上の流れでバウンドするボールの位置と速度が各時刻毎に計算されることになる。

pSwitch

pSwitchもやっていることは基本的にこれを同じであるが、複数の信号関数を扱う点が異なる。 よりわかりやすいpSwitchBでコアな処理を説明する。
型は以下

pSwitchB :: Functor col => col (SF a b) -> SF (a, col b) (Event c) -> (col (SF a b) -> c -> SF a (col b)) -> SF a (col b)

1つ目の引数は信号関数をFunctorでラップしたものだが、具体的にわかりやすく考えるならListだと思えばよい。
2つ目の引数はイベントの発生条件を決めるための信号関数。以下の2つを入力とし発生するイベントを出力とする信号関数を与える。

  • pSwitchBへの入力a
  • 1つ目の信号関数を実行後の出力のList(col b)

3つ目はイベント発生時にスイッチすべき信号関数を決めるもの。決めるために使える情報は、1つ目の引数として渡した信号関数のListと発生したイベントの中身cである。

具体例

emitter :: (Input, Output) -> Event Int
emitter (1, _out) = Event 1
emitter _         = noEvent

update :: [SF Input Output] -> SF Input Output
update sfs = pSwitchB
               sfs
               arr emitter
               (\sfs' n -> update $ updateSFs sfs n)

updateSFs = ...

例えば、複数の物体の動きをシミュレートしたい場面ではこのように書ける。 複数の物体の信号関数をリストとして最初の引数に渡している。emitterでは特定のinputであった場合のみEventを発生させることで、3つ目の引数で指定した関数が実行される。その場合、全ての信号関数と発生したイベントの内容に従って必要な信号関数を更新する処理(updateSFs)を行う。そして再度updateを呼んで次以降の処理に同じ処理を行っていく。

ここでBというのはBroadcastを表す。その意味はすべての信号関数に同じinputを与えるということ。
信号関数毎に受け取るinputを分けたい場合はpSwitchを使う。その場合は第一引数としてinputをroutingする関数を渡すことができる。

追記

pSwitchは現在うまく動かない模様。実際に動かそうとしてみたところ、イベントが発生した瞬間に次の処理に進まなくなってしまった(上記でいうemitterの出力がnoEventの場合は問題なし)。debugログを出力しながらみてみたところ無限ループに陥っている感じだった。と思って調べてみたところgithub上でissueが出ていた。

Infinite Loop When using (d)pSwitchB · Issue #22 · ivanperez-keera/Yampa · GitHub

これとまったく同じ症状だった。

参考