トップ «前の日記(2007-11-19) 最新 次の日記(2007-11-22)» 編集

Route 477



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で囲んでしまうのが簡単だけど…。それによって困ることってあるかな。