restart-case

PCL 読んでて、なんかできそうな気がしたのでやってみた。restart-case のちゃんとした仕様を把握してないけど、それっぽいことができたのでメモ。

;; error 投げると restarter を呼び出す
;; restarter は #:restart-case の block からreturn-from するので
;; 全体として restart-case が返る
* (let (restarter)
    (handler-bind ((condition (lambda (c)
                                (funcall restarter))))
      (block #1=#:restart-case
        (setf restarter (lambda ()
                          (return-from #1#
                            (progn
                              (format t "再起動するお")
                              'restart-case))))
        (error "hey"))))
再起動するお
=> restart-case

;; handler-case で error 処理すると restarter は呼び出されない
* (let (restarter)
    (handler-bind ((condition (lambda (c) (funcall restarter))))
      (handler-case
          (block #1=#:restart-case
            (setf restarter (lambda ()
                              (return-from #1#
                                (progn
                                  (format t "再起動するお")
                                  'restart-case))))
            (error "hey"))
        (error (e)
          (format t "捕まえたお: ~S" e)
          'handler-case))))
捕まえたお: #S(simple-error format-string "hey" format-arguments nil)
=> handler-case

;; handler-case があっても拾えなかったら restarter が呼び出される
* (let (restarter)
    (handler-bind ((condition (lambda (c) (funcall restarter))))
      (handler-case
          (block #1=#:restart-case
            (setf restarter (lambda ()
                              (return-from #1#
                                (progn
                                  (format t "再起動するお")
                                  'restart-case))))
            (error "hey"))
        (type-error (e)
          (format t "捕まえたお: ~S" e)
          'handler-case))))
再起動するお
=> restart-case

どっかに restart 用の関数保存しといて、toplevel まで signal ったら restart 用の関数を選ばせて呼び出すような関数を si::*condition-handlers* に突っ込んでおけば、あとは上のような form になる restart-case を定義しておけばいいかな、と。
ただ、*scratch* とかでやってると、その都度 si::*condition-handlers* を(handler-case とかで)shadow してしまってるみたいなので、試すのがめんどい。