--.--
--
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

05.06
Tue
CLの例外の扱いって凄く柔軟にできるとかよくいわれますよね。

ということでやってみます。


(define-condition invalid-argument-error (error)
((arg :initarg :arg
:accessor arg-of))
(:report
(lambda (condition stream)
(format stream "~%invalid argument: ~S~%"
(arg-of condition)))))


まずコンディションを定義します。意図しない引数で関数が呼ばれた時に

こいつがerrorで通知されることを意図しています.

define-conditionはdefclassでクラスを定義するのと殆ど同じ形式です.

ていうかCLISPでmacroexpandするとdefclassに展開されてますね.

reportに書かれているlambdaは、このコンディションが通知されて

エラーハンドリングされずに、デバッガに落ちるとこいつを使ってメッセージを表示するっぽいです.

どんな引数xをもらってもエラーを吐く関数fooを定義して試してみます

$ sbcl
This is SBCL 1.1.15, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (define-condition invalid-argument-error (error)
((arg :initarg :arg
:accessor arg-of))
(:report
(lambda (condition stream)
(format stream "~%invalid argument: ~S~%"
(arg-of condition)))))

INVALID-ARGUMENT-ERROR
* (defun foo (x)
(error
(make-condition
'invalid-argument-error
:arg x)))

FOO
* (foo 123)

debugger invoked on a INVALID-ARGUMENT-ERROR in thread
#<THREAD "main thread" RUNNING {10038D3173}>:

invalid argument: 123


Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.

(FOO 123)
0]


という風な感じになりました。

さてこいつをデバッガに落ちないようにハンドリングしてやります。

まず他言語にもよくあるtry - catch型のやつです

ソースコードは以下です

(define-condition invalid-argument-error (error)
((arg :initarg :arg
:accessor arg-of))
(:report
(lambda (condition stream)
(format stream "~%invalid argument: ~S~%"
(arg-of condition)))))

(defun foo (x)
(error
(make-condition
'invalid-argument-error
:arg x)))


(defun bar ()
(handler-case
(foo 321)
(invalid-argument-error (condition)
(declare (ignore condition))
(format t "caught an error!"))))

(bar)


こいつをロードさせると caught an error! と表示され、barのハンドラでちゃんとキャッチできたのがわかります.

handler-caseは以下のような感じで書きます.

(handler-case 
コンディションを通知しそうな処理
(コンディションの型1 (コンディションのインスタンス1)
処理1)
(コンディションの型2 (コンディションのインスタンス2)
処理2)
...)


次が書きたかった柔軟だってやつです。他言語にも標準でこんな感じのはあるんでしょうか。

以下がソースコードです

(define-condition invalid-argument-error (error)
((arg :initarg :arg
:accessor arg-of))
(:report
(lambda (condition stream)
(format stream "~%invalid argument: ~S~%"
(arg-of condition)))))

(defun foo (x)
(when (< x 0)
(restart-case
(error
(make-condition
'invalid-argument-error
:arg x))
(negation ()
(setf x (- x)))
(add5 ()
(incf x 5))))
(* x 3))


(defun bar ()
(handler-bind
((invalid-argument-error
(lambda (condition)
(declare (ignore condition))
(invoke-restart 'negation))))
(print (foo -4))))

(bar)


fooとbarに変更があります。

まずfooですが、いつでもコンディションを通知するのではなく、引数xの値が負の場合の時のみになってます。

んで、errorの上をrestart-caseで包んでやり、回復の手段をいくつか定義しておきます。

ここではnegationという、xの正負を反転させるやつとadd5という5を足す方法を用意しました.

(5を足したって正にならない場合はあるのでなんか特別なときに使う用です)

barの方ですがhandler-caseからhandler-bindになってます。

なぜrestart-caseつかうときにhandler-bindにしなきゃいけないかなんですが

こちらのブログが分り易かったのでリンクを貼らせてもらいます。

でこの場合、fooは-4を受け取るのでinvalid-argument-errorコンディションを上位のbarに通知しますが

その際いくつかの回復手段を提供しています。

それをキャッチしたbarはその内の1つを選びinvoke-restartでfooをもっかいやり直させます

今の場合正負が反転するnegationを選んだのでxの値が4になって継続させられます.で結果12が表示されます.

ちなみにrestartを提供しながら適切にハンドリングされずにデバッガに落ちると

以下みたいな感じになります

debugger invoked on a INVALID-ARGUMENT-ERROR in thread
#<THREAD "main thread" RUNNING {10038D3273}>:

invalid argument: -4


Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
0: [NEGATION] NEGATION
1: [ADD5 ] ADD5
2: [RETRY ] Retry EVAL of current toplevel form.
3: [CONTINUE] Ignore error and continue loading file "/home/moratori/Dropbox/works/Programming/Lisp/e2.lisp".
4: [ABORT ] Abort loading file "/home/moratori/Dropbox/works/Programming/Lisp/e2.lisp".
5: Ignore runtime option --load "e2.lisp".
6: Skip rest of --eval and --load options.
7: Skip to toplevel READ/EVAL/PRINT loop.
8: [EXIT ] Exit SBCL (calling #'EXIT, killing the process).

(FOO -4)
0] 0

12


よく見る感じのやつですよね。0〜8までのやつで適切な復帰なり終了なりの処理を選ぶことができます。

0を選んだので12が返りました。



ちょっと疑問に思ったのは、handler-bindはhandler-caseみたいにコンディションの通知の伝播を止めてはくれない

みたいな感じなんですけど、invoke-restartしたときはその結果が返されるって認識でいいのかな?

スポンサーサイト

comment 0 trackback 0
トラックバックURL
http://telracsmoratori.blog.fc2.com/tb.php/186-1e27db17
トラックバック
コメント
管理者にだけ表示を許可する
 
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。