関数 abort と関数 muffle-warning

たぶん自分用メモ。

CLHS ではこうなってるのだけど

If no such restart exists, ..., and the functions abort and muffle-warning signal an error of type control-error.

もし該当する再起動が見つからなければ、...、関数 abort および muffle-warning はエラー control-error を通知する。

CLHS: Function ABORT, CONTINUE, MUFFLE-WARNING...

xyzzy ではどうしようか、という話。

関数 abort と再起動 abort

Common Lisp では

Implementors are encouraged to make sure that there is always a restart named abort around any user code so that user code can call abort at any time and expect something reasonable to happen;

実装者は、ユーザーのコードで関数 abort を呼び出すといつでも妥当な結果になることを期待できるよう、常に再起動 abort があるようにすることを推奨する。

CLHS: Restart ABORT

基本的に常に再起動 abort があることを前提にしてるようだ。

xyzzy では condition-restart があっても(自分で用意してやらん限り)再起動 abort なぞ用意されてないので、そのままだと関数 abort はほとんどの場合においてエラー control-error を投げることになる。

そもそも再起動 abort ってのは何をするものかというと、

The intent of the abort restart is to allow return to the innermost ``command level.''

再起動 abort の狙いは直近の "コマンドレベル" まで戻れるようにすることです。

CLHS: Restart ABORT

xyzzy でこれに近い状況は、ミニバッファからの入力だとか、自前でコマンドループ回してる場合だとか。そういう状況で途中でキャンセルできるように、ということであれば、使うのはエラー quit だろう。ということで再起動 abort が見つからなかったらエラー quit を投げるようにした。

関数 muffle-warning と再起動 muffle-warning

基本的に「警告を発する」には、コンディション warning を関数 warn で投げる。

関数 warn はコンディション warning を投げる*1ときに再起動 muffle-warning を用意しておく。なので関数 warn によって投げられたコンディション warning をハンドラが捕まえた場合は、常に muffle-warning がある。これは condition-restart で上書きした関数 warn でできてる。

関数 error や関数 signal でコンディション warning を投げることもできて、その場合は再起動 muffle-warning を用意してくれないので、関数 muffle-warning で握り潰そうとするとエラー program-error になる。

;; SBCL にて
CL-USER> (handler-bind ((warning 'muffle-warning))
           (warn (make-condition 'simple-warning :format-control "Hey")))
NIL
CL-USER> (handler-case
             (handler-bind ((warning 'muffle-warning))
               (error (make-condition 'simple-warning :format-control "Hey")))
           (control-error (e) e))
#<SB-INT:SIMPLE-CONTROL-ERROR "No restart ~S is active~@[ for ~S~]." {B968A28}>

で、xyzzy + condition-restart の場合。再起動 muffle-warning が見つからなかったら(control-error を継承した)エラー condition-restart::restart-not-found を投げるようになってたのでそのままでおk。*2

(handler-bind ((warning 'muffle-warning))
  (warn (make-condition 'simple-warning :format-string "Hey")))
=> nil

(handler-case
    (handler-bind ((warning 'muffle-warning))
      (error (make-condition 'simple-warning :format-control "Hey")))
  (control-error (e) e))
=> #S(condition-restart::restart-not-found designator muffle-warning condition nil)

というワケで、警告を発する場合は関数 warn を使いましょう。

*1:ちなみに関数 warn はコンディション warning 以外のコンディションだとエラー type-error になる。知らんかった。

*2:export くらいはしといた方がいいかも。