さかもとのブログ

つらつらと

継続理解再開

id:rsakamot:20090502:1241226261では,継続わからないとつぶやいた.

そしてそのまま放置してSICPへと移行した.

SICP4.3では,継続(call/ccは使わず,ひたすら関数を渡す)を用いて,amb評価器を実装した.
細かい動きは理解し切れていない気がするが,おおよその動きは理解できたと思う.

call/ccを理解するのは今だ!と思い,久しぶりにcall/ccへ取り組む.

前回わからなかった,break/next付きfor-eachはすんなり理解できた.これもSICPのおかげ?
特にcatchにはなかなか理解に苦しんだが,今回は理解できたと思う.
以下自分用に説明.

今回の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 )]))

すごーく簡単に説明すると,

  1. catchで,sigと継続kの対を*signals*に登録する.*signals*は連想リストになっている.
  2. throwを実行するときに,*signals*の連想リストから,'sigに該当する対を探し,そのcdrを実行することで,登録されている継続を呼び出す

となる.
今回のサンプルプログラムは以下のようになっている.

(define (div n d)
  (if (= d 0)
        (throw DivideZeroError
               (print "Divide by Zero"))
      (/ n d)))
(define (percentage a b)
  (catch (DivideZeroError
          (print (* (div a b) 100.0) "%"))
         (finally
          (print "follow..."))))

実行すると以下のようになる

;; throwを呼び出さない
gosh> (percentage 1 10)
10.0%
follow...
#<undef>
;; throwを呼び出す
gosh> (percentage 1 0)
Divide by Zero
follow...
#<undef>

このままでは理解しにくいので,percetageを展開し,さらにちょっと追加する.

(define (percentage a b)
  (let* ((signals-backup *signals*)
         (val (call/cc (lambda (k)
                         (set! *signals* (cons (cons 'DivideZeroError k) *signals*))
                         (print (* (div a b) 100.0) "%")
                         'percent-hoge)))) ;追加
       (set! *signals* signals-backup)
       (print "follow ...")
       val))

このpercentageで上のサンプルプログラムを実行すると,

gosh> (percentage 1 10)
10.0%
follow ...
percent-hoge
gosh> (percentage 1 0)
Divide by Zero
follow ...
#<undef>

のようになる.
throwが呼ばれない場合は,通常通りにvalがletによって初期化され,percent-hogeとなる.
throwが呼ばれる場合は,valが,throwの結果#によって初期化される.
つまり,valの初期化が継続地点となっている.