% Macro RESTART-CASE
Macro RESTART-CASE
restart-case
restartable-form {
clause}
=> result*
clause::= (case-name lambda-list
[[:interactive interactive-expression | :report report-expression | :test test-expression]]
declaration* form*)
restartable-form - フォーム
case-name - シンボルか、nil
lambda-list - 通常のラムダリスト
interactive-expression - シンボルか、ラムダ式
report-expression - 文字列か、シンボルか、ラムダ式
test-expression - シンボルか、ラムダ式
declaration - 宣言式。評価されません。
form - フォーム
result - restartable-formの評価の返却値か、
選択されたclause内の最後のformの返却値か、nil
restart-case
は、
clause制御の遷移先として特別な意味を持つ動的環境下で
restartable-formを評価します。
もしrestartable-formの実行が終わり何らかの値を返却したときは、
そのすべての値はrestart-case
の返却値となり処理が完了します
restartable-formが実行中に、
どんなコードでもclauseのどれかに
制御を遷移するかもしれません(invoke-restart
を参照)。
もし遷移が起こったとき、そのclause本体内のformは評価され、
そして最後のformの返却値がrestart-case
によって返却されます。
この場合、動的状態はclauseを実行する前に適切に巻き戻されます
(つまりrestartable-formの周りで確立されたrestart
は
もはや有効ではありません)。
もしformが存在しないclauseが選択された場合、
restart-case
はnil
が返却されます。
もしcase-nameがシンボルなら、それはrestart
の名前です。
同じ名前のcase-nameを使ったclauseは複数存在できます。
このような場合、その名前の最初のclauseが
find-restart
によって発見されます。
他のclauseはcompute-restarts
を使うことでアクセスできます。
各arglistは通常のラムダリスト、
対応するformを実行中に束縛されます。
これらのパラメーターはrestart-case
のclauseが
invoke-restart
呼び出しから必要なデータを受け取って使われます。
標準では、invoke-restart-interactively
は引数を受け取らず、
全ての引数はrestart
から対話式で集めるための
オプションでなければなりません。
しかし、もしinvoke-restart-interactively
へ
どのように正しい引数リストを計算するのかについて
知らせるときに使われる:interactive
キーワードを持っている場合は、
その引数はオプションである必要はありません。
キーワードオプションは次のような意味を持ちます。
:interactive
:interactive
によって指定された値は、
適切な引数を取る関数でなければなりません。
(function value)
が現在のレキシカルな環境上で評価されます。
これは引数なしの関数を返却しなければならず、
その関数が起動されるとinvoke-restart-interactively
によって
使われる引数を返却します。
invoke-restart-interactively
は、
restart
が試行される前にその利用可能な動的環境下で呼び出され、
query-io
がユーザーとの対話で使用されます。
restart
が対話式に呼ばれたものの
:interactive
オプションが指定されていなかったときは、
その引数リストは空リストのものが呼び出しに使われます。
:report
:report
による値がラムダ式かシンボルのとき、
それはfunction
で受け付けられるものでなければなりません。
(function value)
が現在のレキシカルな環境上で評価されます。
これは引数をストリームひとつ取る関数を返却しなければならず、
その関数はrestart
の定義をストリームへ印刷するものです。
この関数はrestart
が印刷されるとき
*print-escape*
がnil
であれば
いつでも呼び出されます。
(lambda (stream) (write-string value stream))
restart
が報告を尋ねられたものの
引数に:report
が指定されていなかったときは、
そのrestart
の名前がデフォルトの報告のテキストを
生成するときに使用されます。
*print-escape*
がnil
のとき、
プリンターはrestart
の報告情報を使います。
例えば、デバッガーはcontinue
コマンドが
入力されたときのアクションを次のように説明するかもしれません。(format t "~&~S -- ~A~%" ':continue some-restart)
:CONTINUE -- Return to command level
restart
に
:report
オプションが提供されていなかったときの
結果は未定義です。
:test
:test
によって指定された値は、
適切な引数を取る関数でなければなりません。
(function value)
が現在のレキシカルな環境上で評価されます。
これは引数をコンディションひとつ取る関数を返却しなければならず、
その関数はもしそのrestart
が見えていると考えられるなら
trueを返却するようなものです。
(lambda (c) (declare (ignore c)) t)
と同等です。
もしrestartable-formがリストであり、
car
部がsignal
, error
, cerror
,
warn
のシンボルののどれか出るとき
(またはマクロフォームで展開したらこのようなリストになるとき)、
with-condition-restarts
が暗黙的に使用され、
指定したrestart
と通知されるコンディションが関連付けられます。
(restart-case
(handler-bind ((error #'(lambda (c)
(declare (ignore condition))
(invoke-restart 'my-restart 7))))
(error "Foo."))
(my-restart (&optional v) v))
=> 7
(define-condition food-error (error) ())
=> FOOD-ERROR
(define-condition bad-tasting-sundae (food-error)
((ice-cream :initarg :ice-cream :reader bad-tasting-sundae-ice-cream)
(sauce :initarg :sauce :reader bad-tasting-sundae-sauce)
(topping :initarg :topping :reader bad-tasting-sundae-topping))
(:report (lambda (condition stream)
(format stream "Bad tasting sundae with ~S, ~S, and ~S"
(bad-tasting-sundae-ice-cream condition)
(bad-tasting-sundae-sauce condition)
(bad-tasting-sundae-topping condition)))))
=> BAD-TASTING-SUNDAE
(defun all-start-with-same-letter (symbol1 symbol2 symbol3)
(let ((first-letter (char (symbol-name symbol1) 0)))
(and (eql first-letter (char (symbol-name symbol2) 0))
(eql first-letter (char (symbol-name symbol3) 0)))))
=> ALL-START-WITH-SAME-LETTER
(defun read-new-value ()
(format t "Enter a new value: ")
(multiple-value-list (eval (read))))
=> READ-NEW-VALUE
(defun verify-or-fix-perfect-sundae (ice-cream sauce topping)
(do ()
((all-start-with-same-letter ice-cream sauce topping))
(restart-case
(error 'bad-tasting-sundae
:ice-cream ice-cream
:sauce sauce
:topping topping)
(use-new-ice-cream (new-ice-cream)
:report "Use a new ice cream."
:interactive read-new-value
(setq ice-cream new-ice-cream))
(use-new-sauce (new-sauce)
:report "Use a new sauce."
:interactive read-new-value
(setq sauce new-sauce))
(use-new-topping (new-topping)
:report "Use a new topping."
:interactive read-new-value
(setq topping new-topping))))
(values ice-cream sauce topping))
=> VERIFY-OR-FIX-PERFECT-SUNDAE
(verify-or-fix-perfect-sundae 'vanilla 'caramel 'cherry)
>> Error: Bad tasting sundae with VANILLA, CARAMEL, and CHERRY.
>> To continue, type :CONTINUE followed by an option number:
>> 1: Use a new ice cream.
>> 2: Use a new sauce.
>> 3: Use a new topping.
>> 4: Return to Lisp Toplevel.
>> Debug> :continue 1
>> Use a new ice cream.
>> Enter a new ice cream: 'chocolate
=> CHOCOLATE, CARAMEL, CHERRY
なし。
なし。
なし。
restart-bind
,
with-simple-restart
,
(restart-case expression
(name1 arglist1 ...options1... . body1)
(name2 arglist2 ...options2... . body2))
上記のフォームは下記のものと同等です。
(block #1=#:g0001
(let ((#2=#:g0002 nil))
(tagbody
(restart-bind ((name1 #'(lambda (&rest temp)
(setq #2# temp)
(go #3=#:g0003))
...slightly-transformed-options1...)
(name2 #'(lambda (&rest temp)
(setq #2# temp)
(go #4=#:g0004))
...slightly-transformed-options2...))
(return-from #1# expression))
#3# (return-from #1#
(apply #'(lambda arglist1 . body1) #2#))
#4# (return-from #1#
(apply #'(lambda arglist2 . body2) #2#)))))
名前なしのrestart
は一般的に対話式でのみ使用され、
:interactive
オプションは定義を持たず小さな値です。
実装は名前なしのrestart
が:report
情報なしで
コンパイル時に提供されていたときは、警告を出すことが推奨されます。
実行時では、このエラーはデバッガーに入ったときに通知されるかもしれません。
エラーを通知すると、
デバッガーに再帰的に入場される可能性があるため
(さらに別の再帰的なエラーを引き起こすなど)、
デバッガーはエラーが起こっても実際にはエラーを通知せず、
その問題の何らかの指示を印刷するようにお勧めします。
(restart-case (signal fred)
(a ...)
(b ...))
==
(restart-case
(with-condition-restarts fred
(list (find-restart 'a)
(find-restart 'b))
(signal fred))
(a ...)
(b ...))