lispとrubyとpythonと その5 例外処理(lisp)

try-finally的なことがしたいのであればunwind-protectでいい。

(defun aaa()
  (unwind-protect 
      (progn 
 (error "例外発生")
 (format t "処理・・・・"))
    (format t "後処理")))

(aaa)

とすれば例外が発生しても(format t "後処理")は必ず実行される。
さらに凝ったことをしたいと思ったらconditionを使うことになる。
他の普通の言語になれてるとconditionはオーバースペックに見えてしょうがない。

conditionを覚えるならここ。
http://cl-www.msi.co.jp/solutions/knowledge/lisp-world/tutorial/condition-system.pdf
これとhyperspecを読んでもwith-condition-restartsの使い方が分からない。

;;condition
;;状態(Exceptionみたいなもの)
;;
;;Signaling
;;conditionの通知(throwみたいなもの
;;
;;handling
;;conditionを受け取る(catchみたいなもの)
;;
;;restart
;;conditionを受けたあとに処理を継続させる機能(よくわからない)

;;代表的なconditionの継承関係
;; condition + serious-condition + error + simple-error
;;           |                   + storange-condition
;;           + warning + simple-warning
;;           + simple-condition



;;conditionの定義
;;(sinal xxx)で使うやつ
;;  (:report (lambda(c s)
;;の中で(call-next-method)がかけるのかと思ったんだけど書いてもエラーになったりするので書かない。
(define-condition my-condition(simple-condition)
  ((msg :accessor my-condition-msg :initform "condition"))
  (:report (lambda(c s)
      (format s "my-condition:~A" (my-condition-msg c)))))

;;(warn xxx)で使うやつ
(define-condition my-warn(simple-warning)
  ((msg :accessor my-warn-msg :initform "warn"))
  (:report (lambda(c s)
      (progn
        (format s "my-warn:~A" (my-warn-msg c))))))

;;(error xxx)で使うやつ
(define-condition my-error(simple-error)
  ((msg :accessor my-error-msg :initform "error"))
  (:report (lambda(c s)
      (format s "my-error:~A" (my-error-msg c)))))

;;signaling
;;conditionを通知
;;
;;signal
;;warn
;;error
;;cerror
;;の4種類の方法がある

;;通知方法
;;singal
;;デバッガは起動しない
;;無視してもいい状態の通知に使う
;;イベントっぽく使える・・・のか?
;;condition通知のコストって安いんだろうか・・・?
;;C#とかの例外処理だとthrowのコストは高いから避けるんだけど。
;;タイプじゃなくて文字列を渡すとsimple-conditionになる
(defun raise-signal()
  (signal (make-condition 'my-condition))
    (format t "signal後の処理。。。"))

(raise-signal)
;;->signal後の処理。。。
;;=>NIL

(defun raise-simple-signal()
  (signal (make-condition 'simple-condition
     :format-control "raise-simple-signalで作ったcondition[~A]"
     :format-argument "raise-simple-signalで指定したargument"))
    (format t "signal後の処理。。。"))

(raise-simple-signal)
;;->signal後の処理。。。
;;=>NIL

;;warn
;;タイプじゃなくて文字列を渡すとsimple-warnになる
;;warnではデバッガは起動しないが*error-output*ストリームにメッセージを出力する
(defun raise-warn()
  (warn (make-condition 'my-warn))
  (format t "warn後の処理。。。~%"))

(raise-warn)
;;->WARNING: my-warn:warn
;:->warn後の処理。。。
;;=>NIL

(defun raise-simple-warning()
  (warn (make-condition 'simple-warning
     :format-control "raise-simple-warningで作ったwarn[~A]"
     :format-arguments (list "raise-simple-waningで指定したargument")))
  (format t "warn後の処理。。。~%"))

(raise-simple-warning)
;;->WARNING: raise-simple-warningで作ったwarn[raise-simple-waningで指定したargument]
;;->warn後の処理。。。
;;=>NIL


;;cerror
;;restartsでcontinueを選択して処理を続行できる
;;continueが選択されるとcerrorはnilを返して処理続行
;;タイプじゃなくて文字列を渡すとsimple-errorになる
(defun raise-cerror()
  (cerror "これを選ぶと処理を続行する" (make-condition 'my-error))
  (format t "cerror後の処理。。。~%"))

;;(raise-cerror)
;;raise-cerrorを呼び出すとデバッガが立ち上がって以下のような表示がでる
;; my-error:error
;;    [Condition of type MY-ERROR]
;;
;; Restarts:
;;  0: [CONTINUE] これを選ぶと処理を続行する
;;  1: [ABORT] Return to SLIME's top level.
;;  2: [TERMINATE-THREAD] Terminate this thread (#<THREAD "new-repl-thread" RUNNING {AB508C9}>)
;;
;; Backtrace:
;;   0: (RAISE-CERROR)
;;   1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (RAISE-CERROR) #<NULL-LEXENV>)
;;
;;0でcontinueすると以下のようになる
;;->cerror後の処理。。。
;;=>NIL

(defun raise-simple-cerror()
  (cerror "これを選ぶと処理を続行する" (make-condition 'simple-error
    :format-control "raise-cerrorで作ったerror[~A]"
    :format-arguments (list "raise-cerrorで指定したargument")))
  (format t "cerror後の処理。。。~%"))

;;(raise-simple-cerror)
;;これもデバッガが上がるけど省略
;;contnueを選ぶと以下となる
;;->cerror後の処理。。。
;;=>NIL

;;error
;;errorで通知したconditionがhandleされなかったらデバッガが起動する
;;conditionじゃなくて文字列を渡すとsimple-errorになる
(defun raise-error()
  (error (make-condition 'my-error))
  (format t "error後の処理。。。~%"))

;;(raise-error)
;;デバッガが上がる。こんな感じ。
;; my-error:error
;;    [Condition of type MY-ERROR]

;; Restarts:
;;  0: [ABORT] Return to SLIME's top level.
;;  1: [TERMINATE-THREAD] Terminate this thread (#<THREAD "new-repl-thread" RUNNING {B564EE9}>)

;; Backtrace:
;;   0: (RAISE-ERROR)
;;   1: (RAISE-ERROR)[:EXTERNAL]
;;   2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (RAISE-ERROR) #<NULL-LEXENV>)
;;
;;abortにしろtarminate-threadにしろ処理を終える

(defun raise-simple-error()
  (error (make-condition 'simple-error 
    :format-control "raise-simple-errorで作ったerror[~A]"
    :format-arguments (list "raise-simple-errorで指定したargument")))
  (format t "error後の処理。。。~%"))

;;(raise-simple-error)
;;これもデバッガがあがる
;;abortにしろtarminate-threadにしろ処理を終える

;;*break-on-signals*
;;signalやwarnではデバッガ起動しないが、*break-on-signals*
;;を利用してデバッガを起動することができる
;;*break-on-signals*に引っ掛けたいconditionのシンボルを束縛しとけば引っ掛けてくれるようになる
(defun test-*break-on-signals*()
  (let ((*break-on-signals* 'my-condition))
    (raise-signal)))

;;break
;;関数の途中で強制的にデバッガを起動
(defun call-break()
  (format t "...1~%")
  (break "break....")
  (format t "...2~%"))

;;invoke-debugger
;;conditionを引数にとってデバッガを起動
;;signalingを行わない
;;handleしてその中でデバッガ起動するためのものなのかなぁ。。。
(defun call-invoke-debugger()
  (invoke-debugger (make-condition 'my-error)))

;;*debugger-hook*
;;デバッガが起動する直前に実行する関数を指定
;;・・・らしいんだけどなんかうまくうごかない
(defun test-*debugger-hook* ()
  (let ((*debugger-hook* #'my-debugger-hook))
    (raise-error)))
        
(defun my-debugger-hook(c x)
  (format t "my-debugger-hook ~A:~A~%" c x))
   
;;handling
;;conditionをうけとる
;;catchみたいなもの
;;3つのマクロがある
;;handler-bind
;;handler-case
;;ignore-errors

;;handler-bind
;;handleしたconditionが登録されたものに一致するか継承したものであればハンドラ実行
;;登録されたハンドラを左から実行
;;実行後はさらに上位のハンドラに通知
(defun test-handler-bind1 (fun)
  (handler-bind ((my-condition #'(lambda (c)
       (format t "handler condition 1 ~A~%" c)))
   (my-warn #'(lambda (c)
     (format t "handler warning 1 ~A~%" c)))
   (my-error #'(lambda (c)
          (format t "handler error 1 ~A~%" c))))
    (format t "start test-handler-bind1~%")
    (test-handler-bind2 fun)
    (format t "end test-handler-bind1~%")))


(defun test-handler-bind2 (fun)
  (handler-bind ((my-condition #'(lambda (c)
       (format t "handler condition 2 ~A~%" c)))
   (my-warn #'(lambda (c)
         (format t "handler warning 2 ~A~%" c)))
   (my-error #'(lambda (c)
          (format t "handler error 2 ~A~%" c))))
    (format t "start test-handler-bind2~%")
    (funcall fun)
    (format t "end test-handler-bind2~%")))
;;test-handler-bind1を呼び出してcontinueを選んだ場合の出力

(test-handler-bind1 #'(lambda ()
   (raise-signal)))
;;->start test-handler-bind1
;;  start test-handler-bind2
;;  handler condition 2 my-condition:condition
;;  handler condition 1 my-condition:condition
;;  signal後の処理。。。end test-handler-bind2
;;  end test-handler-bind1
;;=>NIL

(test-handler-bind1 #'(lambda ()
   (raise-warn)))
;;->start test-handler-bind1
;;  start test-handler-bind2
;;  handler warning 2 my-warn:warn
;;  handler warning 1 my-warn:warn
;;  WARNING: my-warn:warn
;;  warn後の処理。。。
;;  end test-handler-bind2
;;  end test-handler-bind1
;;=>NIL

(test-handler-bind1 #'(lambda ()
   (raise-cerror)))
;;->start test-handler-bind1
;;  start test-handler-bind2
;;  handler error 2 my-error:error
;;  handler error 1 my-error:error
;;  *デバッガが表示される*
;;  cerror後の処理。。。
;;  end test-handler-bind2
;;  end test-handler-bind1
;;=>NIL
;;continueを選ぶと例外発生の次の処理から継続
;;handler後の処理から継続ではないから注意

;;プログラムからcontinueさせるとこうなる
(defun test-handler-bind3 ()
  (handler-bind ((my-condition #'(lambda (c)
       (format t "handler warning 1 ~A~%" c)
       (continue c)))
   (my-warn #'(lambda (c)
         (format t "handler warning 1 ~A~%" c)
         (continue c)))
   (my-error #'(lambda (c)
          (format t "handler error 2 ~A~%" c)
          (continue c))))
    (format t "start test-handler-bind3~%")
    (raise-cerror)
    (format t "end test-handler-bind3~%")))
;;->start test-handler-bind3
;;  handler error 2 my-error:error
;;  cerror後の処理。。。
;;  end test-handler-bind3
;;=>NIL

;;handler-case
;;handler-caseはhandleしたあとに上位に投げない
;;java、C#のtry-catchに近いのはこっち
(defun test-handler-case1(fun)
  (handler-case 
      (progn
 (format t "start test-handler-case1~%")
 (test-handler-case2 fun)
 (format t "end test-handler-case1~%"))
    (my-condition (c)
      (format t "handler condition 1 ~A~%" c))
    (my-warn (c)
      (format t "handler warning 1 ~A~%" c))
    (my-error (c)
      (format t "handler error 1 ~A~%" c))))

(defun test-handler-case2(fun)
  (handler-case 
      (progn
 (format t "start test-handler-case2~%")
 (funcall fun)
 (format t "end test-handler-case2~%"))
    (my-confition (c)
      (format t "handler condition 2 ~A~%" c))
    (my-warn (c)
      (format t "handler warning 2 ~A~%" c))
    (my-error (c)
      (format t "handler error 2 ~A~%" c))))

;; CL-USER> (test-handler-case1)
;; start test-handler-case1
;; start test-handler-case2
;; handler error 2 my-condition:error
;; end test-handler-case1
;; NIL


;;ignore-errors
;;errorかそれを継承したクラスのsignalを拾ってnilをかえす
;;通知されたerrorは多値の2値目として返ってくる
;;エラーがあったときはnil扱いすればいい、ってときは便利
(defun test-ignore-errors ()
  (multiple-value-bind (ret cond)
      (ignore-errors (raise-cerror))
    (format t "return:~A~%" ret)
    (format t "error:~A~%" cond)))
;; CL-USER> (test-ignore-errors)
;; return:NIL
;; error:my-condition:error
;; NIL



;;restart
;;処理の再開

;;restart-bind
;;restart-case
;;with-simple-restart
;;with-condition-restarts
;;がある
;;restart-bindはrestart-caseなんかが内部で使ってるプリミティブなものらしいので割愛
;;with-condition-restartsは使い方わかんない・・・・。
;;だれか教えて。

;;restart-case
(defun test-restart-case1(fun)
  (restart-case
      (progn
 (format t "start test-restaort-case1~%")
 (funcall fun)
 (format t "end test-restart-case1~%"))
    (リスタート1 ()
      (format t "リスタート1を実行~%")
      "リスタート1")
    (リスタート2 ()
      (format t "リスタート2を実行~%")
      "リスタート2")
    (リスタート3 ()
      (format t "リスタート3を実行~%")
      "リスタート3")))

(test-restart-case1 #'(lambda () (raise-error)))
;;->start test-restaort-case1
;;こんな感じで選択肢が表示される、
;;errorの時だけ。warn、signalではでない
;; my-error:error
;;    [Condition of type MY-ERROR]

;; Restarts:
;;  0: [リスタート1] リスタート1
;;  1: [リスタート2] リスタート2
;;  2: [リスタート3] リスタート3
;;  3: [ABORT] Return to SLIME's top level.
;;  4: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-thread" RUNNING {A8B48E9}>)

;; Backtrace:
;;   0: (RAISE-ERROR)
;;   1: (RAISE-ERROR)[:EXTERNAL]
;;   2: (TEST-RESTART-CASE1 #<FUNCTION (LAMBDA NIL) {B11C0AD}>)
;;   3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (TEST-RESTART-CASE1 (FUNCTION (LAMBDA NIL #))) #<NULL-LEXENV>)    
;;0を選ぶとこうなる
;;->リスタート1を実行
;;=>"リスタート1"

;;invoke-restrt
;;restartをコードからよびだす
(test-restart-case1 #'(lambda () (invoke-restart 'リスタート1)))
;;->start test-restaort-case1
;;  リスタート1を実行
;;=>"リスタート1"

;;compute-restartsで今のコンテキストで有効なrestartのリストがとれる
(test-restart-case1 #'(lambda () 
   (format t "active restart~%~A~%" (compute-restarts))
   (invoke-restart 'リスタート1)))
;;->start test-restaort-case1
;;  active restart
;;  (リスタート1 リスタート2 リスタート3 Return to SLIME's top level.
;;   Terminate this thread (#<THREAD "repl-thread" RUNNING {A8B48E9}>))
;;  リスタート1を実行
;;=>"リスタート1"