표현식 래퍼: #%expression

표현식 래퍼: #%expression

폼(form)을 표현식으로 강제로 파싱하게 해 주는 #%expression을 살펴볼게요. 재귀적 정의 컨텍스트에서 뒤따르는 정의의 컴파일 타임 정보를 현재 표현식에 활용해야 할 때 특히 유용해요.

출처: Racket Reference

본문

syntax

(#%expression expr)

expr과 같은 결과를 만들어요. #%expression을 쓰면 폼을 표현식으로 파싱하도록 강제해요.

예시를 볼게요:

> (#%expression (+ 1 2))
3
> (#%expression (define x 10))
eval:8:0: define: not allowed in an expression context

  in: (define x 10)

#%expression 폼은 재귀적 정의 컨텍스트에서 유용한데, 거기서 뒤따르는 정의를 확장하는 것이 현재 표현식에 컴파일 타임 정보를 제공할 수 있기 때문이에요. 예를 들어, 주어진 식별자에 심볼 몇 개를 컴파일 타임에 단순히 기록하는 define-sym-case 매크로를 생각해 봐요.

(define-syntax (define-sym-case stx)
  (syntax-case stx ()
    [(_ id sym ...)
     (andmap identifier? (syntax->list #'(sym ...)))
     #'(define-syntax id
         '(sym ...))]))

그리고 표현식에 쓰인 심볼들이 앞서 정의된 것과 일치하는지 확인하는 case의 변형을 생각해 봐요:

(define-syntax (sym-case stx)
  (syntax-case stx ()
    [(_ id val-expr [(sym) expr] ...)
     (let ()
       (define expected-ids
         (syntax-local-value
          #'id
          (λ ()
            (raise-syntax-error
             'sym-case
             "expected an identifier bound via define-sym-case"
             stx
             #'id))))
       (define actual-ids (syntax->datum #'(sym ...)))
       (unless (equal? expected-ids actual-ids)
         (raise-syntax-error
          'sym-case
          (format "expected the symbols ~s"
                  expected-ids)
          stx))
       #'(case val-expr [(sym) expr] ...))]))

정의가 이렇게 사용 뒤에 따라온다면, define-sym-case 매크로는 id를 바인딩할 기회를 갖지 못하고 sym-case 매크로가 오류를 신호해요:

> (let ()
    (sym-case land-creatures 'bear
              [(bear) 1]
              [(fox) 2])
    (define-sym-case land-creatures bear fox))
eval:11:0: sym-case: expected an identifier bound via
define-sym-case

  at: land-creatures

  in: (sym-case land-creatures (quote bear) ((bear) 1)
((fox) 2))

하지만 sym-case#%expression으로 감싸면, 확장기는 그것이 표현식임을 알기 위해 확장할 필요가 없어지고 define-sym-case 표현식으로 넘어가요.

> (let ()
    (#%expression (sym-case sea-creatures 'whale
                            [(whale) 1]
                            [(squid) 2]))
    (define-sym-case sea-creatures whale squid)
    'more...)
'more...

물론, sym-case 같은 매크로는 클라이언트가 #%expression을 직접 추가하도록 요구하면 안 돼요. 대신 인자의 기본 형태를 확인한 뒤, syntax-local-value를 호출하고 확장을 마무리하는 헬퍼 매크로를 감싼 #%expression으로 확장해야 해요.

더 알아보기