2007-11-20
■ [biwascheme] sleepを実装した
BiwaSchemeに、指定したミリ秒だけ実行を停止する関数「sleep」を実装した。アニメーション等には必須の機能である。
sleepが簡単じゃない
普通の言語であればsleepなんて簡単なわけだが、ことJavascriptにおいては、sleepの実装は自明ではない。 Javascriptに「指定した時間だけ待つ」という命令はなく、setTimeoutで一定時間後に指定した関数を実行することしかできないからだ。 (無限ループでむりやり待つことは可能だが、その間ブラウザが固まってしまうのでやってはいけない^^;)
というわけで、sleep前にインタプリタの状態を保存し、setTimeoutで待ったあとにインタプリタを再開するようにした。
sleep関数の定義はこんな感じになった。pauseという変数がインタプリタの途中の状態(スタック及び評価中の値)を保持している。
define_libfunc("sleep", 1, 1, function(ar){ var msec = ar[0]; return new WebScheme.Pause(function(pause){ setTimeout(function(){ pause.resume(nil) }, msec); }); });
計算結果が行方不明
さて、ここで一つ問題が発生する。
(begin (display "a") (sleep 1000) 1)
というような式を評価したとき、インタプリタは1を結果として返すべきである。しかし、今までのように
var result = biwascheme.evaluate(exp);
としただけでは、1ではなくPauseのインスタンスが返ってしまう。最終的な計算結果はsetTimeoutの後にしか分からないので、 evaluate関数からは知ることができないのである。
というわけで、ちょっと呼び出し方法を変えて、以下のように書くことにした。
biwascheme.evaluate(exp, function(result){ puts(result); });
式の実行前に「計算が終わった後の処理」をインタプリタに登録し、pauseオブジェクトを作るときにはこれも保存するようにする。 で、計算が終わったときにこの処理を呼び出してやる。
これで、上のような式でもちゃんと1が表示されるようになった。めでたしめでたし。
beginがないとき
では、全体を囲むbeginがなく、
(display "a") (sleep 1000) 1
と書かれていた場合はどうするべきだろうか?
今の実装では、REPLでこれを実行すると
a 1 undefined
という実行結果になる。
実際、Gaucheでも同じような結果になるので、Schemeの意味論的にはこれでも良いようだ。
でも、実用を考えると、beginがあるときと同じように 「a→(1秒後)→1」と表示されてほしい気もする。 これを実装するなら、全体を強制的にbeginで囲んでしまうのが簡単だけど…。それによって困ることってあるかな。