WITH-SIMPLE-RESTART — 간단한 restart를 걸어두는 매크로

WITH-SIMPLE-RESTART — 간단한 restart를 걸어두는 매크로

프로그램이 오류 상황에 빠졌을 때, 사용자에게 '이 지점에서 포기하고 돌아갈래요?' 같은 선택지를 제공하고 싶다면 restart를 써야 해요. CLHS의 restart 매커니즘은 강력하지만 restart-case를 직접 쓰려면 문법이 조금 무거워요. 아주 자주 쓰는 단순한 경우를 간편하게 만든 것이 with-simple-restart예요. 이 문서는 매크로(macro) 문서예요.

출처: CLHS WITH-SIMPLE-RESTART

본문

문법 (Syntax)

with-simple-restart (name format-control format-argument*) form*
=> result*

인자와 값 (Arguments and Values)

  • name — 심볼(symbol)이에요.
  • format-control — format control이에요.
  • format-argument — 객체(object), 즉 format 인자예요.
  • forms — implicit progn이에요.
  • results — 정상 상황에서는 forms가 반환하는 값들이에요. 예외 상황에서 name이라는 restart가 호출되면 두 값, 즉 nilt를 반환해요.

설명 (Description)

with-simple-restart는 restart 하나를 확립해요.

forms를 실행하는 동안 name이 가리키는 restart가 호출되지 않으면, 마지막 forms가 반환하는 모든 값을 반환해요. name이 가리키는 restart가 호출되면 제어가 with-simple-restart로 옮겨지고, 이 폼은 두 값 nilt를 반환해요.

namenil이면 익명(anonymous) restart가 확립돼요.

format-controlformat-arguments는 restart를 보고(report)하는 데 쓰여요.

예제 (Examples)

오류가 났을 때 '명령 레벨로 돌아가기', '명령 레벨에서 빠져나오기' 같은 선택지를 제시하는 예를 볼게요.

(defun read-eval-print-loop (level)
  (with-simple-restart (abort "Exit command level ~D." level)
    (loop
      (with-simple-restart (abort "Return to command level ~D." level)
        (let ((form (prog2 (fresh-line) (read) (fresh-line))))
          (prin1 (eval form)))))))
=>  READ-EVAL-PRINT-LOOP
 (read-eval-print-loop 1)
 (+ 'a 3)
>>  Error: The argument, A, to the function + was of the wrong type.
>>         The function expected a number.
>>  To continue, type :CONTINUE followed by an option number:
>>   1: Specify a value to use this time.
>>   2: Return to command level 1.
>>   3: Exit command level 1.
>>   4: Return to Lisp Toplevel.

두 번째로, nil을 이름으로 써 익명 restart를 만드는 예시예요. 값이 너무 커지면 에러를 내고, 아래 함수가 그 restart를 통해 something big으로 대체해요.

(defun compute-fixnum-power-of-2 (x)
  (with-simple-restart (nil "Give up on computing 2^~D." x)
    (let ((result 1))
      (dotimes (i x result)
        (setq result (* 2 result))
        (unless (fixnump result)
          (error "Power of 2 is too large."))))))
COMPUTE-FIXNUM-POWER-OF-2
 (defun compute-power-of-2 (x)
   (or (compute-fixnum-power-of-2 x) 'something big))
COMPUTE-POWER-OF-2
 (compute-power-of-2 10)
1024
 (compute-power-of-2 10000)
>>  Error: Power of 2 is too large.
>>  To continue, type :CONTINUE followed by an option number.
>>   1: Give up on computing 2^10000.
>>   2: Return to Lisp Toplevel
>>  Debug> :continue 1
=>  SOMETHING-BIG

compute-fixnum-power-of-2nilt를 반환하면 or가 그다음 표현식 'something big을 평가해서 SOMETHING-BIG를 돌려주는 흐름이에요.

부수 효과 (Side Effects)

없음.

영향받는 요소 (Affected By)

없음.

예외 상황 (Exceptional Situations)

없음.

더 알아보기 (See Also)

  • restart-case

참고 (Notes)

with-simple-restartrestart-case의 가장 흔한 용법 중 하나를 줄여 놓은 문법이에요.

with-simple-restart는 다음과 같이 정의할 수 있어요.

(defmacro with-simple-restart ((restart-name format-control
                                             &rest format-arguments)
                               &body forms)
  `(restart-case (progn ,@forms)
     (,restart-name ()
         :report (lambda (stream)
                   (format stream ,format-control ,@format-arguments))
        (values nil t))))

예외 상황에서 두 번째 반환 값이 t이므로, 보통은(항상은 아니지만) 정상 상황의 두 번째 반환 값을 생략하거나 nil로 만들어 두 상황을 구분하는 게 흔해요.