プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(35)

本日も「Land of Lisp」です。
昨日は変な突っ込み方していたEmacsの設定も整理できたので最高に気分がいいです。
今日は13章、Webサーバを作り始めます。

Common Lispでのエラー処理

Webサーバのように、外の世界とやりとりする時は、常に予想外の事態が起こり得ます。
でも大丈夫!Common Lispにだって例外機能がしっかりと備わっているそうです。

関数の中で何か不味いことが起きてしまったとき、
Lisp関数はコンディションを通知することで実行環境に問題が起きたことを知らせてくれます。
自分で書くコードから直接コンディションを通知したい場合は、errorコマンドを使えばいいそう。
他の場所でエラー通知を横取りしていなければ、
プログラムの実行を中断してくれるとのことなので、試してみます。

CL-USER> (error "foo")
; Evaluation aborted on #<SIMPLE-ERROR "foo" {100190E6D3}>.
CL-USER>

エラーメッセージが返ってきました。

Stop FOOing around, numbskull!
   [Condition of type FOO]

しかし、こんな風にただテキストを返すだけではどれぐらい役に立つのか怪しいものです。
でもやっぱり大丈夫、自前のコンディションを定義することだって出来ます。

CL-USER> (define-condition foo () ()
           (:report (lambda (condition stream)
                      (princ "Stop FOOing around, numbskull!" stream))))
; in: DEFINE-CONDITION FOO
;     (FUNCALL
;      #'(LAMBDA (CONDITION STREAM)
;          (PRINC "Stop FOOing around, numbskull!" STREAM))
;      CONDITION STREAM)
; ==>
;   (SB-C::%FUNCALL
;    #'(LAMBDA (CONDITION STREAM)
;        (PRINC "Stop FOOing around, numbskull!" STREAM))
;    CONDITION STREAM)
; 
; caught STYLE-WARNING:
;   The variable CONDITION is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition
FOO
CL-USER> (error 'foo) 
; Evaluation aborted on #<FOO {1001E786E3}>.
CL-USER>

warningがいっぱい出ました。使ってないよ!というのがほとんどですかね。
エラーメッセージはこんなんでした。

Stop FOOing around, numbskull!
   [Condition of type FOO]

なるほど、これでコンディションの型ごとによりわかりやすくメッセージを表示できるわけですね。

コンディションを横取りする

define-conditionでコンディション型を定義したときに与えた名前を使って、
この型のコンディションが通知された時に、プログラムを中断するかわりに実行する処理を
プログラムの上位層で書いておくことが出来るそうです。
そのためのコマンドは、handler-caseというもの。

CL-USER> (handler-case (bad-function)
           (foo () "somebody signaled foo!")
           (bar () "somebody signaled bar!"))
; in: HANDLER-CASE (BAD-FUNCTION)
;     (SB-IMPL::%HANDLER-BIND
;      ((FOO (LAMBDA (SB-IMPL::TEMP) (DECLARE #) (GO #:TAG564)))
;       (BAR (LAMBDA (SB-IMPL::TEMP) (DECLARE #) (GO #:TAG566))))
;      (RETURN-FROM #:BLOCK568 (#:FORM-FUN-570)))
; --> SB-INT:DX-FLET FLET SB-INT:DX-LET LET CONS LIST CONS 
; --> SB-INT:NAMED-LAMBDA FUNCTION 
; ==>
;   (TYPEP SB-IMPL::C 'BAR)
; 
; caught STYLE-WARNING:
;   undefined type: BAR
; 
; compilation unit finished
;   Undefined type:
;     BAR
;   caught 1 STYLE-WARNING condition
"somebody signaled foo!"
CL-USER>

色々と出てきてはいますが、プログラムが中断されることはなくfooに指定した処理が行なわれました。
handler-caseは、引数に取るのが対象のプログラム、本体にこの型のコンディションならこれ、
という感じで書いていくっぽいですね。
ちょっと変わったif、という覚えかたは雑かもしれませんが、そんなイメージです。

予想外のコンディションからリソースを保護する

予想外の例外事態が発生したときに何が起こり得るのか。
例えば、ファイルやソケットストリームに何かを書いている途中に例外が発生したら...
するとストリームが開きっぱなしになってしまい、解放してやらないと
それらの資源がずっと使用中ということになり、最悪リブートしないと使えないなんてことになるそうです。

そういった問題を避けるためにあるのが、unwind-protectコマンドだそうです。
これは、Common Lispコンパイラに「これだけは何があっても実行してくれ」と伝えるためのものだそう。

CL-USER> (unwind-protect (/ 1 0)
           (princ "I need to say 'flubyduby' matter what"))
; in: UNWIND-PROTECT (/ 1 0)
;     (/ 1 0)
; 
; caught STYLE-WARNING:
;   Lisp error during constant folding:
;   arithmetic error DIVISION-BY-ZERO signalled
;   Operation was (/ 1 0).
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition
I need to say 'flubyduby' matter what; Evaluation aborted on #<DIVISION-BY-ZERO {10027C2033}>.
CL-USER>

なんと0除算を敢行しています。
当然エラーです。でも最後にこっそり、princで指定した文字列が出力されています。
Common Lispの"with-"マクロは内部でunwind-protectを呼んでくれていることが多く、
直接unwind-protectを呼ばなければいけないことはそんなにないそうです。
安心して"with-"マクロを使っていこうと思います。

なんだか昨日はあまり眠れなかったのでとても眠いです。
ここまでにします。おやすみなさい。