handler-bind — 조건을 보고 직접 다루는 핸들러 설치

handler-bind — 조건을 보고 직접 다루는 핸들러 설치

handler-case가 "오류가 나면 해당 절로 점프해서 끝내는" 방식이라면, handler-bind는 좀 더 근본적으로 조건을 다룰 핸들러 함수를 직접 설치하는 방식이에요. 조건이 신호되면 핸들러 함수는 그 조건을 인자로 받아 호출되고, 필요한 판단을 직접 할 수 있어요.

출처: CLHS 공식 문서 - HANDLER-BIND

본문

handler-bind는 지정한 핸들러 묶음이 적용된 동적 환경에서 forms를 실행해요. 각 binding(type handler) 형태로, type은 조건 타입, handler는 그 타입의 조건을 받아 처리할 함수로 평가되는 폼이에요.

handler-bind ({binding}*) form*
=> result*

binding ::= (type handler)

핸들러 함수는 인자를 정확히 하나, 즉 신호된 조건을 받아요. 조건이 신호되면 바인딩들은 위에서 아래로 순서대로 검색되고(마치 typecase처럼), 맞는 타입이 발견되면 그 핸들러가 실행돼요. 이때 핸들러는 이 바인딩들이 보이지 않는 동적 환경에서 실행돼서, 재귀적인 오류를 피해요. 핸들러가 처리하지 않기로 하면(decline) 검색은 남은 핸들러 쪽으로 이어져요.

(handler-bind ((unbound-variable #'(lambda ...))
               (error #'(lambda ...)))
  ...)

이 코드에서 본문 중에 처리되지 않은 unbound-variable 오류가 신호되면 첫 번째 함수가, 다른 종류의 오류가 나면 두 번째 함수가 호출돼요. 어떤 경우든 그 함수 안의 코드를 실행하는 동안 두 핸들러 모두 활성 상태가 아니에요.

적절한 핸들러를 몾 찾으면 바깥쪽 동적 범위에서 핸들러를 찾고, 그래도 없으면 error는 디버거로 들어가요. 아래는 handler-bind로 오류를 잡아 catch/throw로 빠져나가는 관용적인 예시예요.

(defun trap-error-handler (condition)
  (format *error-output* "~&~A~&" condition)
  (throw 'trap-errors nil))

(defmacro trap-errors (&rest forms)
  `(catch 'trap-errors
     (handler-bind ((error #'trap-error-handler))
       ,@forms)))

(list (trap-errors (signal "Foo.") 1)
      (trap-errors (error  "Bar.") 2)
      (+ 1 2))
>>  Bar.
=>  (1 NIL 3)

Foo.는 출력되지 않아요. signal이 만든 조건은 단순 조건(simple condition)이라 error 타입이 아니어서, trap-errors가 설치한 error 핸들러를 건드리지 않기 때문이에요. 반면 error로 신호된 Bar.는 error 타입이라 핸들러가 받아 출력하고 thrownil을 돌려줘요.

함께 보기

handler-case