bitterharvest’s diary

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

Reactive Bananaで学ぶリアクティブ・プログラミング(1)

1.退職というイベント

2か月近くもブログが中断してしまったが、別れの季節の年度末を迎えたためである。多くの大学では卒業論文を課している。私が所属している大学も、そのようになっているので、追い込みとなる1月は、その指導に多くの時間がとられる。今年は、学生たちにReactive Programmingで、ロボットやゲームのプログラムを作るようにと注文を出した。学生たちにとっては、レベルが少し高すぎたようで、1月は厳しい修練の場となった。

通常は、卒論発表が終了すると、入学試験が始まり、教員は楽になるのだが、今回は、私自身が大学を退職することになったので、その準備が必要になった。退職するとき、多くの先生は最終講義をするが、いろいろなしがらみから、私自身もすることになった。しかし、これには、学生が卒論に取り組む以上に、労力を費やした。

日常生活のなかに数学やプログラミングの話題を織り込んだ随筆は、これは別れの記念に配布しようと思ったものだが、暮れまでにはその作業を終了していた。この随筆を編集しなおして、最終講義用に再構成しなおそうと思ったのだが、最終講義の目的にかなうようにまとめることが、困難であると感じた。最終講義は特別な講義で、対象者は、同僚の教員、一緒に仕事をしてくれた職員、勉強・研究を一緒にした在校生と卒業生たち、仕事を通しての知人、時には有名人である。知識も興味も異なる人々を相手に、1時間という長い時間を楽しんでもらう講義にするのには、大変な準備を必要とすることが、作業を進めていく中で理解した。

プレゼンテーション用の資料を何度も作り直して、準備万端で、2月最後の日に最終講義を行った。満足のいく最終講義ができたと自分では思っているのだが、どうであっただろうか。

2.モデル・プログラム

前置きが長くなったが、本題に入ることにしよう。Reactive Bananaについては、これに付属している例題を用いて、これまで、10回にわたり紹介した。ここでは、Reactive Bananaをもう少し詳しく知るために、体系的に説明する。

Reactive Bananaの理解を助けるために、Reactive.Banana.Modelというモジュールが用意されている。そのソースプログラムが用意されているので、これをダウンロードして用いる(ダウンロードせずに、importで用いたら、理由はよくわかっていないのだが、うまくいかなかった。Eventの定義が、Newtypeではなく、Dataに変わっていた)。

3.イベント

リアクティブ・プログラミングでは、イベントと振舞いが重要な概念である。まず、イベントの方から先に説明すると、モジュールでは次のように定義されている。

-- | Event is modeled by an /infinite/ list of 'Maybe' values.
-- It is isomorphic to @Time -> Maybe a@.
--
-- 'Nothing' indicates that no occurrence happens,
-- while 'Just' indicates that an occurrence happens.
newtype Event a = E { unE :: [Maybe a] } deriving (Show)

Eventの役割は、時間を与えたときに発生するイベントを与えることである。そこで、ここでのEventの定義では、時間Timeの時、Maybe aというイベントが発生するものとする。

上記プログラムの詳細を説明しよう。Eventが型コンストラクタである。これは、Eventという新しい型を作る。Eventの右横にあるaは型引数である。

等号の右側にあるEが値コンストラクタである。これを用いて、Eventという新しい型の値を作成する。{}は、この型がレコード構文であることを示している。ここでのレコード構文は、ただ一つのフィールドとして、unEを有する。そのフィールドはMaybe aのリストである。

時間0ではJust 0が、時間1ではJust 1が、時間2ではJust 2が、時間3ではJust 3が、時間4ではJust 4というイベントが、発生するとして、今、Maybe aのリストl1として以下のものを定義したとしよう。

*Reactive.Banana.Model> let let l1 = [Just 0, Just 1, Just 2, Just 3, Just 4]

これを型Eventの値とするには次のようにする。

*Reactive.Banana.Model> let e1 = E l1

フィールドunEの値はもちろん次のようになる。

*Reactive.Banana.Model> unE e1
[Just 0,Just 1,Just 2,Just 3,Just 4]

時間とイベントとの関係を見るために、関数forgetEを利用する。この関数は、特別な時間より前に生じたすべてのイベントを、忘れてしまう。
この関数は次のように定義されている。

-- Forget all event occurences before a particular time
forgetE :: Time -> Event a -> [Maybe a]
forgetE time (E xs) = drop time xs

それでは、値e1で時間0より前のことを忘れることにする。

*Reactive.Banana.Model> forgetE 0 e1
[Just 0,Just 1,Just 2,Just 3,Just 4]

もちろんすべてのイベントの列が出力される。
それでは、時間1より前のことを忘れることにする。

*Reactive.Banana.Model> forgetE 1 e1
[Just 1,Just 2,Just 3,Just 4]

正しく機能していることが分かる。

ここでは、イベントのリストを有限としたが、型Eventは無限のリストを持つことが許される。そこで、時間nではn番目の素数が出力されるようなEventの値を定義することにしよう。素数は次のように求めることができる。

*Reactive.Banana.Model> let primes = filterPrime [2..] where filterPrime (p:xs) = p:filterPrime [x | x <-xs, x `mod` p /= 0]

この関数が正しいかどうかを見るために、最初の10個の素数を取り出してみる。

*Reactive.Banana.Model> take 10 primes
[2,3,5,7,11,13,17,19,23,29]

素数のリストをMaybe型にするには次のようにすればよい。

*Reactive.Banana.Model> let l2 = fmap Just primes

確認してみる。

*Reactive.Banana.Model> take 10 l2
[Just 2,Just 3,Just 5,Just 7,Just 11,Just 13,Just 17,Just 19,Just 23,Just 29]

うまくいっていることが分かる。

それでは、これをEvent型にしてみよう。

*Reactive.Banana.Model> let e2 = E l2

時間10より前のイベントを忘れてしまおう。

*Reactive.Banana.Model> let e2 = E l2

これは無限のリストなので、最初の10個を取り出してみよう。

*Reactive.Banana.Model> take 10 e3
[Just 31,Just 37,Just 41,Just 43,Just 47,Just 53,Just 59,Just 61,Just 67,Just 71]

うまくいっていることが分かる。