しょーもない再起動の使い方: typo

"apply" をよく "appyl" と typo して undefined-function とか怒られるので、もうちょっと気を利かせてくれないかな、とこんなことをした。

(defun appyl (fn &rest args)
  (restart-case
      (error 'undefined-function :name 'appyl)
    (apply ()
      :report "「ごめんなさい、また typo しました。apply してください」"
      (apply #'apply fn args))))
=> appyl

これで typo して undefined-function と怒られた時に apply 再起動を選べばそのまま apply してくれる。

ついでなので一般化してみた。

;; xyzzy でやってるのでこうだけど、common-lisp だとちょっと syntax が違ったはず
(define-condition typo (program-error)
  (name)
  (:report (lambda (typo stream)
             (format stream "Oh, typo... ~S"
               (typo-name typo)))))

(defmacro deftypo (typo correct)
  `(defun ,typo (&rest args)
     (restart-case
         (error 'typo :name ',typo)
       (correct ()
         :report (lambda (stream)
                   (format stream "Invoke correct function `~S'." ',correct))
         (apply ',correct args)))))
(deftypo appyl apply)
=> appyl

(appyl (lambda (a b) (+ a b)) '(1 3))
; typo:
;     Oh, typo... appyl
; 
;   0 [correct ] Invoke correct function `apply'.
;   1 [abort   ] やめる。
; 
; Restart[0-1]: 0
=> 4

ぉぅぃぇ。

いちいち再起動選ぶのもめんどいので warning 出しつつ勝手に修正するマクロも。

(define-condition typo-corrected (warning)
  (name)
  (:report (lambda (typo-corrected stream)
             (format stream "typo `~S' auto corrected."
               (typo-corrected-name typo-corrected)))))

(defmacro with-auto-correct-typos (&body body)
  `(handler-bind ((typo (lambda (typo)
                          (warn 'typo-corrected
                                :name (typo-name typo))
                          (let ((r (find-restart 'correct typo)))
                            (when r (invoke-restart r))))))
     ,@body))

と思ったけど condition-restart のだと warning でも再起動選択が出てきてしまった。ぐむむ。

(with-auto-correct-typos
  (appyl (lambda (x) (* x x)) '(3)))
; typo-corrected:
;     typo `appyl' auto corrected.
; 
;   0 [muffle-warning] この警告は無かったことに。
;   1 [abort   ] やめる。
;
; Restart[0-1]: 0
=> 9