2014-03-16-01-Common Lisp のコンディション機構-3 - project-enigma

2014-03-16-01-Common Lisp のコンディション機構-3

>> Site top >> weblog >> 月別アーカイブ >> 2014年03月のlog >> 2014-03-16-01-Common Lisp のコンディション機構-3

最終更新日付:2014/03/16 23:50:00


Common Lisp のコンディション機構-3

2014 年 03 月 16 日

前回まででコンディションの通知と捕捉は終わりにして、今度は再起動について。

前回までのコードは以下のようなものだった。

(define-condition divide-by-zero (error)
  ((arg1 :initarg :arg1
         :reader  divide-by-zero-arg1)
   (arg2 :initarg :arg2
         :reader  divide-by-zero-arg2)))

(defun test-divide (a b)
  (when (zerop b)
    (error 'divide-by-zero :arg1 a :arg2 b))
  (/ a b))

(defun test-driver (a b)
  (handler-case (test-divide a b)
    (divide-by-zero (err)
      (format t "WARNING : divide by zero. arg1 = ~A, arg2 = ~A.~%"
              (divide-by-zero-arg1 err) (divide-by-zero-arg2 err))
      :infinite)))

投入されたコンディションを handler-case で受けて処理する場合、コールスタックは完全に巻き戻されるらしい。だから、例えばトップレベルに近い場所にある handler-case が奥深くから投入されたコンディションを処理する場合、処理を「やりなおす」のは事実上不可能だ。投入された場所か、その直近の呼出し元あたりで「やりなおしの手段」を用意しておき、もっと上のあたりでそのどれかを選ぶ、というのが再起動の考え方のようだ。

では、まずは test-driver とは別のモノを作ろう。restart-driver1 では handler-case を使わず、restart-case を使っていくつかの再起動の手段を提供する。

(defun restart-driver1 (a b)
  (restart-case (test-divide a b)
    (divide-by-1  ()    a)
    (use-value    (val) val)
    (use-infinite ()    :infinite)))

restart-driver1 経由で test-divide がコールされる時、test-divide は restart-driver1 がセットアップした3つの再起動(divide-by-1 / use-value / use-infinite)が控えている。この状態で restart-driver1 を(ゼロ除算が発生するように)コールするとどうなるか。

* (restart-driver1 10 0)
; => Condition DIVIDE-BY-ZERO was signalled.

test-divide を直接コールした場合と同じように divide-by-zero が通知された。しかし、直接コールと違うのは、デバッガによる選択肢に restart-driver1 がセットアップした3つの再起動が追加されていることだ。では、デバッガに落ちないようにするため、さらに上位のコードを追加しよう。今度は handler-case ではなく、handler-bind を使用する必要があるらしい。

(defun restart-driver2 (a b)
  (handler-bind ((divide-by-zero
                  (lambda (err)
                    (format t "WARNING : divide by zero. arg1 = ~A, arg2 = ~A.~%"
                            (divide-by-zero-arg1 err) (divide-by-zero-arg2 err))
                    (invoke-restart 'divide-by-1)
                    ;(invoke-restart 'use-value 666)
                    ;(invoke-restart 'use-infinite)
                    )))
    (restart-driver1 a b)))

上記の3つの invoke-restart(2つはコメントアウトされている)を入れ替えれば、使用される再起動が変化する。handler-bind は handler-case とは異なり、関数(か lambda 式)を渡さなければならないらしい。それは、handler-bind によってセットアップされるハンドラがスタックを巻き戻さずに呼び出されるからということらしい。

中途半端だけど残りは改めて。

 

コメント

このページにコメントする

 

このページのタグ

Page tag : Common Lisp

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.