with-exception-handler の仕様を正しく理解しよう その2

(with-exception-handler handler thunk)

Handler must be a procedure and should accept one argument.
Thunk must be a procedure that accepts zero arguments.
The with-exception-handler procedure returns the results of invoking thunk.

handler は1引数、thunkは引数なしの手続き。with-exception-handler は thunk の呼び出しの結果を返す。

Handler is installed as the current exception handler for the dynamic extent
(as determined by dynamic-wind) of the invocation of thunk.

handler は thunk の呼び出しの動的存続期間 の current exception handler として設定される。
この動的存続期間は dynamic-wind によって決定される。→分からない

Implementation responsibilities:
The implementation must check the restrictions on handler to the extent performed
by applying it as described when it is called as a result of a call to raise or raise-continuable.
An implementation may check whether handler is an appropriate argument before applying it.

実装の責任。
raise や raise-continuable の呼び出しの結果として handler が呼ばれたとき、実装はハンドラの実行による存続期間の制限をチェックしなければならない。→うーん。保留。
実装はハンドラを適用する前にハンドラが適切なものかチェックしても良い。


分からないことが多いが。raise と raise-continuable に行ってみて理解を深めよう。

(raise obj)

Raises a non-continuable exception by invoking the current exception handler on obj.

obje を引数に current exception handler を呼び出すことで non-continuable な例外を上げる。

The handler is called with a continuation whose dynamic environment is that of the call to raise,
except that the current exception handler is
the one that was in place when the handler being called was installed.

handler は raise を call した動的環境を保持する continuation の元で call される。
current exception handler が今まさに call されている handler がインストールされたときに設定されていた handler の場合はこの限りではない。
うーん。これは苦しい。

When the handler returns, a non-continuable exception with condition type &non-continuable is
raised in the same dynamic environment as the handler.

handler から return すると、non-continuable な例外が handler と同じ動的環境で &non-continuable 型のコンディションとして上がる。

(raise-continuable obj)

Raises a continuable exception by invoking the current exception handler on obj.

obj を引数に current exception handler を呼びだし、continuable な例外を上げる。

The handler is called with a continuation that is equivalent to
the continuation of the call to raise-continuable, with these two exceptions:

handler は raise-continuable の call の継続と同一の継続の元で call される。ただし2つの例外がある。

(1) the current exception handler is the one that was in place when the handler being called was installed,

current exception handler が今まさに call されている handler がインストールされたときに設定されていた handler の場合。

and (2) if the handler being called returns, then it will again become the current exception handler.

今まさに call されている handler が return した場合、それは再び current exception handler になる。

If the handler returns,
the values it returns become the values returned by the call to raise-continuable.

handler が return すると return された値が raise-continuable の call の return する値になる。

さてと

どうしようか。Gauche のドキュメントを読んでみよう。
http://gauche.sourceforge.jp/doc/gauche-refj_92.html

;; 低レベルな例外メカニズムの概念的な実装
;; %xhは例外ハンドラのリスト

(define (current-exception-handler) (car %xh))

(define (raise exn)
  (receive r ((car %xh) exn)
    (when (uncontinuable-exception? exn)
      (set! %xh (cdr %xh))
      (raise (make-error "returned from uncontinuable exception")))
    (apply values r)))

(define (with-exception-handler handler thunk)
  (let ((prev %xh))
    (dynamic-wind
      (lambda () (set! %xh (cons handler %xh)))
      thunk
      (lambda () (set! %xh prev)))))

uncontinuable-exception? の部分は仕様の違いだから気にしない。
ポイントは

  • current-exception-handler は handler のリストとして保持されていること
  • with-exception-handler が dynamic-wind を利用して実装できること
  • リストのトップが current-exception-handler として使われること

かな。

ちょっと分からないことが多いが dynamic-wind に行ってみよう。
dynamic-wind に関わる記述が多いし。

dynamic-wind

長いので翻訳の助けを借りよう。
http://practical-scheme.net/wiliki/wiliki.cgi?R6RS%3a%E7%BF%BB%E8%A8%B3%3aR6RS%3a11.15%20Control%20features

before、 thunk、 after は手続きでなければならず、それぞれ 0 個の引き数を取る。
これらの手続きは任意の個数の値を返してもよい。
dynamic-wind 手続きは thunk を引き数なしで呼び出し、この呼び出しの結果を返す。
さらに、 dynamic-wind の呼び出しは thunk への呼び出しの動的存続期間突入するに場合に
常に before を引き数なしで呼び出し、 thunk への呼び出し動的存続期間から脱出するときに
常に after を引き数なしで呼び出す。
したがって、 call-with-current-continuation によって作成された脱出手続きの呼び出しがない場合には、
dynamic-wind は before、 thunk、 after をこの順番で呼び出す。

分かりやすい。

before や after への呼び出しが thunk の動的存続期間内にはないと解釈される一方で、
thunk の呼び出しの動的存続期間内に現れる他の任意の dynamic-wind の呼び出しの before と after の呼び出しは、 
thunk の動的存続期間内にあるものと解釈される。

これも直感とあう。

より精確に言うと、脱出手続きは 0 個以上の活性な dynamic-wind の呼び出し x ... の集合の動的存続期間の外側に制御を移し、 
0 個以上の活性な dynamic-wind の呼び出し y ... の集合の動的存続期間の内側に制御を移す。
最も最近の x の動的存続期間はそのままに、対応する after 手続きを引き数なしで呼び出す。 
after 手続きから返った場合、次に最も最近設定された x を開始し、というように続けていく。
各 x が一度このように扱われると、脱出手続きは最も外側の y に対応する before 手続きを引き数なしで呼び出す。 
before 手続きが返ると、脱出手続きはより内側の y の動的存続期間に再入し、次の y に進む、というようにしていく。
各 y が一度このように扱われると、脱出手続きにパッケージ化された継続に制御が移る。

これは絵に描いた方が俄然よく分かる。
下のような図の Extent C 内から Extent Y に脱出する場合。
after(C) -> after(B) -> after(A) -> before(Z) -> Before(Y) のように呼ばれていくということ。

(図は後で掲載します)

実装系の義務: 実装系は thunk と after が実際に呼び出される場合にだけ、その制約を確認しなければならない。

これはよく分からないなあ。

続く。

追記

この動的存続期間は dynamic-wind によって決定される。

さてこれが何となく分かるようになった。
つまり dynamic-wind は動的存続期間(スタートとエンド)を決めるということだ。
でも dynamic-wind が一度も呼び出されず淡々と実行中の場合は期間はないよね。この場合どうなの?

raise や raise-continuable の呼び出しの結果として handler が呼ばれたとき、
実装はハンドラの実行による存続期間の制限をチェックしなければならない。→うーん。保留。

これは相変わらず分からない。

current exception handler が今まさに call されている handler がインストールされたときに
設定されていた handler の場合はこの限りではない。

相変わらず分からない。

昨日分からなかった。これは分かるようになった。

call/cc は dynamic-wind によって作られた暗黙的なコンテクストにアクセスする。

call/cc で動的存続期間に再入した際に、dynamic-wind の after/before の列を参照するという意味。