bitterharvest’s diary

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

ゲームの中で信号関数を自在に扱う:Yampaの原理を理解する

1.信号関数の原理

前回の記事「シューティングゲーム手習い:複数投石(一石二鳥ならぬ二石一鳥)」で、投石された石と飛んでいる鳥の軌跡を描くプログラムを作成した。

続きの記事として、石と鳥がぶつかった瞬間を描きたいのだが、これには、石と鳥が衝突したというイベントを発生させる必要がある。さらに、衝突したという事実を知って、これまでの石と鳥の運動方程式を変化させる必要がある。付け焼刃でこれまで信号関数(前の記事ではシグナル関数となっていたが今後は信号関数とする)を説明してきた。しかし、原理を理解することなしにこれ以上の説明は無理なので、ここでは、信号関数の原理を説明する。

Haskellにおける関数は純粋関数である。純粋関数という意味は、どのような環境下においても、その関数に与える入力が同じであれば、同じ出力を与えることである。例えば、その時の気分に応じて、朝の挨拶が異なるような人(関数)は、純粋な人(純粋関数)とは言わずひねくれた人(非純粋関数)である。Haskellはこのようなものを排除している(しかし、実世界を扱おうとするとこうはいかない。ゲームの世界でも同じことが言える)。

ゲームはキーボードやマウスを利用して動的な変化を楽しむ遊びである。ゲームの中に確率的な要素が入っていれば、同じようにキーボードやマウスを操作したところで、同じような結果を得ることはできない。このため、ゲームをHaskellのような純粋関数型の言語で記述することは難しいように思われる。実際、ゲームのプログラムは命令型のプログラミング言語で書かれている。しかし、一方で、命令型のプログラムでは、記述されている計算の原理の追跡は極めて困難である。

ゲームのプログラムを詳しく調べると、そこには法則が潜んでいることがわかる。落下するボールにはニュートン力学の法則が、同一点に向かう物質の間では衝突という原理がある。従って、ゲームといえども、普遍の原理・原則を組み合わせて構成されているといえる。そこで、普遍の原理・原則(純粋な関数)を前面に出して、ゲームのプログラムを作成するようにすれば、とても論理的なプログラムができるのではないかと考えられる。この普遍の原理・原則を表したのが信号関数である。

信号関数は、入力と出力、作用と影響、検診と結果などの関係を表す。即ち、信号関数は、何らかの作用を与えることとそれから得られるものの関係を表したものである。何らかの作用を与えることとそれから得られるものは、時間であるかもしれないし、物理的な力であるかもしれないし、はたまた、もっと抽象的なものであるかもしれない。これは、その時何を対象にしているかによって決まる。

そこで、信号関数はHaskellの記法に従って、F a bと記述する。Fは信号関数型であり、二つのフィールドaとbを持つ。この場合には、型変数となっている。型変数aとbは、プログラムの中で、ある型に束縛される。例えば、時間を表す型をTimeとし、これに束縛されたとすると、Timeのすべてのインスタンスaとbにおいてということになる。もっとも新しい数学であるHomotopy Type Theoryに従って記述すれば、a,b:Timeとなる(Homotopy Type Theoryでは、aとbはtermと呼ばれ、Timeはtype、即ち、型と呼ばれる。また、aとbにはそれらを結ぶ道が存在すると解釈される。すなわち、同じ型に属するという意味で同値とみなされる)。

2.Yampaでの信号関数

信号関数を実現した方法にはいくつかある。Yampaはその中の一つである。Yampaの特徴は一階信号関数を最初に定義し、信号を二の次にしていることである。他の実現方法では、信号を先に定義したり、信号発生器を最初に定義したりしている。Yampaは、信号関数を最初に定義することで、プログラム作成時、実行時におこる様々な問題を回避している。詳しいことは、Yampaの説明資料を見て欲しい。Yampaは静かな川の流れが激流に代わる場所をいう(随分と古い映画になるが、1954年にマリリン・モンローが出演した「帰らざる河」で、カナダ・バンフの「ボー滝」がロケ地となっているが、映画の題名の通り、戻ることの不可能な激流となっている。写真は2年前に訪れたときの様子)。
f:id:bitterharvest:20141024213244j:plain

まず信号関数を直感的に定義する。信号に依存した関数を下図に示す。
f:id:bitterharvest:20141024132708p:plain
信号は\(Signal \ \alpha \approx Time \rightarrow \alpha\)
である。即ち、信号は\(\alpha\)で表され、それは時間を写像したものである。
信号関数は直感的に次のように表すことができる。
\(F \ \alpha \ \beta \approx Signal \ \alpha \rightarrow Signal \ \beta\)
信号関数は\(F \ \alpha \ \beta\)で表され、それは、信号\(Signal \ \alpha\)を信号\(Signal \ \beta\)に写像したものである。
上の図で、\(x,y,f\)はHaskellでの型シグネチャで表すと次のようになる。

x :: Signal T1 
y :: Signal T2 
F :: SF T1 T2

さらに、出力は因果関係を表せるようにする。即ち、時間\(t\)での出力は区間\([0,t]\)での入力によるものとする。出力が時間\(t\)にしか依存しない場合には「状態がない」あるいは「純粋である」という。逆に、区間\([0,t]\)に依存するのであれば、「状態がある」あるいは「純粋でない」という。

分かりやすくするために、直感的に説明したが、もう少し厳密に定義すると下図のようになる。
f:id:bitterharvest:20141024132750p:plain
信号関数は状態をカプセル化し、\(state(t)\)は入力の履歴\(x(t’)\)、但し、 \(t’ \in [0,t]\)を要約したものである。
また、\(y(t)\)が\(x(t)\)と\(state(t)\)に依存する場合は状態があり、\(y(t)\)が\(x(t)\)にのみ依存する場合は状態はない。