マクロと call/cc を使った catch と throw の実装の解説を備忘録として残します．



( define *signals* '()) ( define-syntax catch ( syntax-rules ( finally ) [( _ ( sig body ... ) ( finally follow ... )) ( let* (( signals-backup *signals* ) ( val ( call/cc ( lambda ( k ) ( set! *signals* ( cons ( cons ' sig k ) *signals* )) body ... )))) ( set! *signals* signals-backup ) follow ... val )] [( _ ( sig body ... )) ( catch ( sig body ... ) ( finally ))])) ( define-syntax throw ( syntax-rules () [( _ sig val ) (( cdr ( assq ' sig *signals* )) val )]))

( define ( div n d ) ( if ( = d 0 ) ( throw DivedeZeroError ( print #` "ERROR: Divide Zero Error Occured...

divide ,n by ZERO!

--------------------" )) ( / n d ))) ( define ( percentage a b ) ( catch ( DivedeZeroError ( print ( * ( div a b ) 100.0 ) "%" )) ( finally ( print "follow ..." ))))





処理の流れを説明します．

*signal* の初期値を signals-backup に束縛します．

その後 *signal* には (set! *signal* ...) の行で car にシグナル， cdr に継続を渡すk，というペアのリストが束縛されます．

((sig . k)) という形になります．

サンプルコードでは ((DivisedZeroError . k)) が入ります．

その後 body が実行されます．

サンプルコードでは percentage の最初の print 行．

ここで div が呼ばれます．

(= d 0) のが真の時に throw が呼ばれます．

(cdr (assq 'sig *signals*)) が評価され継続を表す k が返され， (k val) で throw の print 行を実行し，戻り値が catch のvalに束縛されます．

（※ twitterでkeenさん(@blackenedgold)さんから，継続を渡す k ではなく，継続を表す k と教えていただいたので訂正しました．）

ここではまってたのですが， (= d 0) なのにシグナルが違っていた場合は (cdr #f) となりエラーが返ります.

シグナルを間違えるなって話ですね．

(= d 0) が #f の時は (/ n d) が実行され値が返ります．



catch に返ってくると *signals* にシグナルの初期値を戻します．

そして finally ... を実行します．

最後にvalに束縛した値を返して終了です． ここでは percentage は print してるので #<undef> が返ります．



こうして一つ一つ追えばそんなに難しくないですね．

でも理解するのに時間かかりました．．．

最後に実行例紹介して終わります．





gosh> (percentage 1 40) 2.5% follow ... #<undef> gosh> (percentage 10 0) ERROR: Divide Zero Error Occured... divide 10 by ZERO! -------------------- follow ... #<undef>



マクロもcall/ccも難しい