`ABORT`·`CONTINUE`·`MUFFLE-WARNING`·`STORE-VALUE`·`USE-VALUE` — 리스타트 전환 함수

ABORT·CONTINUE·MUFFLE-WARNING·STORE-VALUE·USE-VALUE — 리스타트 전환 함수

커먼 리스프의 오류 처리는 그냥 프로그램을 멈추는 데서 끝나지 않아요. 실행 지점을 특정 지점으로 되돌리는 리스타트(restart) 라는 이름표를 걸 수 있고, 이 다섯 함수는 각각 같은 이름을 가진 리스타트로 제어권을 넘기는 역할을 해요.

출처: Common Lisp HyperSpec — Function ABORT, CONTINUE, MUFFLE-WARNING, STORE-VALUE, USE-VALUE

본문

시그니처

abort &optional condition            => |
continue &optional condition         => nil
muffle-warning &optional condition   => |
store-value value &optional condition => nil
use-value value &optional condition  => nil

인자와 반환값

  • value — 어떤 객체.
  • condition — 조건 객체(condition object), 또는 nil.

설명

이 함수들은 각자 자신과 같은 이름을 가진, 가장 최근에 설립된 적용 가능한 리스타트로 제어권을 넘겨요. 즉 abort는 적용 가능한 abort 리스타트를 찾고, continue는 적용 가능한 continue 리스타트를 찾는 식이에요.

해당하는 리스타트가 없으면 행동이 갈라져요.

  • continue, store-value, use-valuenil을 반환해요.
  • abortmuffle-warningcontrol-error 타입의 오류를 신호해요.

conditionnil아닐 때는, 그 조건과 명시적으로 연관된 리스타트나 어떤 조건에도 연관되지 않은 리스타트만 고려돼요. 즉 주어진 조건의 요소가 아닌 조건들의 비어 있지 않은 집합에 연관된 리스타트는 제외돼요. conditionnil이면 모든 리스타트가 고려돼요.

예제

리스타트의 실제 쓰임은 오류 처리 매크로(handler-bind, restart-case)와 함께일 때 빛나요. abort-on-error 매크로는 error를 잡으면 그 자리에서 abort 리스타트로 되돌리는 예예요.

;;; ABORT 리스타트 예제
(defmacro abort-on-error (&body forms)
  `(handler-bind ((error #'abort))
     ,@forms))                  =>  ABORT-ON-ERROR
(abort-on-error (+ 3 5))        =>  8
(abort-on-error (error "You lose."))
>>  Returned to Lisp Top Level.

continue 리스타트는 디버거에서 어떤 옵션을 선택했을 때 그 지점으로 이어서 진행하도록 도와줘요.

;;; CONTINUE 리스타트 예제
(defun real-sqrt (n)
  (when (minusp n)
    (setq n (- n))
    (cerror "Return sqrt(~D) instead." "Tried to take sqrt(-~D)." n))
  (sqrt n))

(real-sqrt 4)                     =>  2
(real-sqrt -9)
>>  Error: Tried to take sqrt(-9).
>>  To continue, type :CONTINUE followed by an option number:
>>   1: Return sqrt(9) instead.
>>   2: Return to Lisp Toplevel.
>>  Debug> (continue)
>>  Return sqrt(9) instead.
=>  3

(handler-bind ((error #'(lambda (c) (continue))))
  (real-sqrt -9))                 =>  3

muffle-warning은 경고를 무시하고 실행을 이어가고 싶을 때 써요.

;;; MUFFLE-WARNING 리스타트 예제
(defun count-down (x)
  (do ((counter x (1- counter)))
      ((= counter 0) 'done)
    (when (= counter 1)
      (warn "Almost done"))
    (format t "~&~D~%" counter)))
=>  COUNT-DOWN
(count-down 3)
>>  3
>>  2
>>  Warning: Almost done
>>  1
=>  DONE

(defun ignore-warnings-while-counting (x)
  (handler-bind ((warning #'ignore-warning))
    (count-down x)))
=>  IGNORE-WARNINGS-WHILE-COUNTING
(defun ignore-warning (condition)
  (declare (ignore condition))
  (muffle-warning))
=>  IGNORE-WARNING
(ignore-warnings-while-counting 3)
>>  3
>>  2
>>  1
=>  DONE

store-valueuse-value는 오류가 났을 때 잘못된 값을 대신할 값을 정하는 데 써요. 아래 예제는 바인딩되지 않은 변수를 읽으려다 오류가 났을 때, 값을 "이번만 쓰기"(use-value) 또는 "앞으로 저장해 쓰기"(store-value)로 선택하게 하는 예시예요.

;;; STORE-VALUE 와 USE-VALUE 리스타트 예제
(defun careful-symbol-value (symbol)
  (check-type symbol symbol)
  (restart-case (if (boundp symbol)
                    (return-from careful-symbol-value
                                 (symbol-value symbol))
                    (error 'unbound-variable
                           :name symbol))
    (use-value (value)
      :report "Specify a value to use this time."
      value)
    (store-value (value)
      :report "Specify a value to store and use in the future."
      (setf (symbol-value symbol) value))))
(setq a 1234)                     =>  1234
(careful-symbol-value 'a)         =>  1234
(makunbound 'a)                   =>  A
(careful-symbol-value 'a)
>>  Error: A is not bound.
>>  Debug> (use-value 12)
=>  12
(careful-symbol-value 'a)
>>  Error: A is not bound.
>>  Debug> (store-value 24)
=>  24
(careful-symbol-value 'a)         =>  24

use-value로 고른 값은 이번 호출만 유효하고, store-value로 고른 값은 실제로 바인딩에 저장되어 다음 호출부터는 그 값이 그대로 쓰이는 차이를 눈여겨볼 만해요.

함께 보기

  • handler-bind, restart-case, restart-bind 등 리스타트 관련 매크로
  • control-error 조건