2011-11-29
■ [biwascheme][memo] call/ccとホスト言語
C言語でScheme処理系を実装する場合、Cで書いたライブラリ関数の中からSchemeの関数を呼び出したいことはままある。*1
int some_library_function(VALUE args) { ... call_scheme_function(func); ... }
このとき問題になるのが、「funcの中で継続がキャプチャされたらどうするのか?」という問題。いやキャプチャが問題なんじゃなくて、それが呼び出されたらどうするのか、という話だけど。
これはCとSchemeに限らず、callccのある言語を実装する他のケースにも当てはまる。CとRubyとか。JavaScriptとSchemeとか。
Gaucheの場合、「呼び出すのは問題ないけど、Cまで戻ってしまったらエラーにするので、その前に大域脱出するなどしてね」とリファレンスに書いてあった。
(つづくか?)
*1 例を挙げれば、(map (lambda () ...) list)とかいくらでもある
■ [biwascheme][scheme] SRFI18を読んだ
いま自分にとって大事なのは
- 各スレッドは固有の動的環境スタックをもつ
- before/afterフックは、そのdynamic-windが実行されたときの動的環境をもつとする
という2点かな。後者は実装上。前者はイベントハンドラ+例外の仕様を考えるのに。
■ [biwascheme] BiwaSchemeにはスレッドがあるか
あるとも言えるし、ないとも言える。
基本的にBiwaSchemeはSchemeプログラムを上から順番に実行する。
実装上は、以下のようなケースでインタプリタの実行が「分断」されることはある。
(print "hello, ") (let1 name (http-request "/name")) ;; Ajax呼び出し。プログラムの残りの部分は、別のJavaScript functionで実行される (print name))
けれども、BiwaSchemeのhttp-requestは同期的 (サーバがデータを返すまで、プログラムの続きを実行しない) なので、 Schemeレベルでは単に「上から順に実行される」と考えてよい。2つのSchemeコードが同時に動くわけではない。
問題はイベントハンドラだ。
以下のようにして*1、2つのScheme関数をイベントハンドラとして登録した場合、何が起きるだろう。
(.. ($ "#btn") (mousedown on-btn-mousedown)) ;; JSでいうと「$("#btn").mousedown(func);」 (.. ($ "#btn") (mouseup on-btn-mouseup))
ボタンの上でマウスボタンが押された際にon-btn-mousedownが、離された際にon-btn-mouseupが実行される。 ユーザのクリックが素早ければ、両者は連続して実行されることになる。
JavaScriptの実行はシングルスレッドで行われる。つまり、(JavaScriptレベルで)2つのfunctionは同時に実行されることはなく、 またプリエンプティブに別のfunctionに実行が移ることもない。(だからあるイベントハンドラですごく重い処理を書くと、その間別のイベントハンドラは実行できない)
ならば、on-btn-mousedownとon-btn-mouseupも同時に実行されることはないように思える。だが、ここで先ほどのhttp-requestの例を思い出してほしい。
(define (on-btn-mousedown e) (print 1) (http-request "/notify/down") (print 2))
この手続き*2の実行は、Ajax呼出しによって複数のfunctionに分断される。 それはつまり、print 1とprint 2の間で実行されるfunctionが切り替わるかもしれないということだ。
(define (on-btn-mouseup e) (print 3) (http-request "/notify/up") (print 4))
マウスを素早くクリックしたとき、「1 2 3 4」と表示されるか、「1 3 2 4」と表示されるか、あるいは「1 3 4 2」と表示される(/notify/downが重かった場合)かは分からない。
これは、見方によっては「2つのイベントハンドラは並列実行される」と言える。
このことを利用すれば、JavaScriptのsetTimeout(func, 0)というイディオムを使って擬似的なスレッドを作ることができる。
(define (start-thread! thunk) (timer thunk 0)) ;; JSの「setTimeout(thunk, 0);」 (start-thread! (lambda () (let loop () (print "hello") (sleep 1) (loop)))) (start-thread! (lambda () (let loop () (print "world") (sleep 1) (loop))))
「擬似的」というのは、sleepやhttp-requestを呼ばないと「スレッド」が切り替わらない(プリエンプティブでない)から。
まとめ
- JavaScriptはシングルスレッドで実行され、複数のfunctionが同時に走ることはなく、またプリエンプティブに別のfunctionに制御が移ることもない。
- しかしSchemeレベルにおいては、複数の手続きが交互に平行に実行されることはある。sleepやhttp-request等の呼出し*3が、「スレッド」が切り替わるタイミングとなる。
ということは
実装的に考えると、BiwaSchemeにはスレッドが「ある」ものとして考えないといけない。うーん、めんどくさいけど、そうはいっても 「サーバとの通信中はマウスクリックが反応しない」みたいなAPIはあり得ないし。
現状の実装だと、実際に上のようなコード書いたときに、スタック破壊されるんじゃないかなぁ。試してないけど。
いっそ上のstart-thread!みたいなものを最初から提供してしまって、「イベントハンドラは別のthread上で動くものと見なされます」としてしまった方が すっきりするかもな。
なんでこんなことを考えてたかというと
dynamic-windを実装しようとしてるから。
ところで
上の疑似スレッドのサンプルコードはコマンドラインのBiwaSchemeでも動かすことができます:-D
Node.jsとnpmをインストール後:
$ npm install biwascheme $ biwas pseudo_thread.scm tick? tick! tick? tick! ...