トップ «前の日記(2011-11-24) 最新 次の日記(2011-12-12)» 編集

Route 477



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!
...

*1 って、0.6.0ではまだこの文法実装してないけど

*2 Scheme界では関数のことを手続き(procedure)と呼んだりする

*3 実装的にいうとBiwaScheme.Pauseを使っている箇所