정의: define, define-syntax, ...
정의: define, define-syntax, ...
Racket에서 이름에 값을 바인딩하는 정의(definition) 폼들을 알아봐요. define, define-values, define-syntax 등 다양한 변형과 함께, require/provide 매크로를 정의하는 확장 폼까지 다룹니다.
출처: Racket Reference
본문
3.14 정의: define, define-syntax, ...
The Racket Guide의 Definitions: define에서 정의를 소개합니다.
(define id expr)(define (head args) body ...+)head | = | id | | (head args) args | = | arg ... | | arg ... . rest-id arg | = | arg-id | | [arg-id default-expr] | | keyword arg-id | | keyword [arg-id default-expr]
첫 번째 폼은 id를 expr의 결과에 bind하고, 두 번째 폼은 id를 프로시저에 bind합니다. 두 번째 경우에 생성되는 프로시저는 (CVT (head args) body ...+)인데, 다음과 같이 정의된 CVT 메타함수를 사용합니다:
(CVT (id . kw-formals) . datum) = (lambda kw-formals . datum)
(CVT (head . kw-formals) . datum) = (lambda kw-formals expr)
if (CVT head . datum) = expr
내부 정의 문맥(internal-definition context)에서는 define 폼이 지역 바인딩을 도입합니다 (〔Internal Definitions〕 참고). 최상위 수준에서는 expr을 평가한 뒤, 아직 없다면 id의 최상위 바인딩을 만들고, (컴파일된 정의와 연결된 namespace에서) id의 최상위 매핑을 동시에 그 바인딩으로 설정합니다.
define의 liberal expansion을 허용하는 문맥에서는, expr이 키워드 인자를 가진 즉시 lambda 폼이거나 args에 키워드 인자가 포함되면 id가 syntax로 바인딩됩니다.
예시:
(define x 10)
x
; 10
(define (f x)
(+ x 1))
(f 10)
; 11
(define ((f x) [y 20])
(+ x y))
((f 10) 30)
; 40
((f 10))
; 30
(define-values (id ...) expr)
expr을 평가하고, 결과의 개수가 id의 개수와 일치하면 그 결과들을 순서대로 id들에 bind합니다. expr이 다른 개수의 결과를 만들면 exn:fail:contract 예외가 일어납니다.
내부 정의 문맥(〔Internal Definitions〕 참고)에서는 define-values 폼이 지역 바인딩을 도입합니다. 최상위 수준에서는 expr을 평가한 뒤, 아직 없다면 각 id의 최상위 바인딩을 만들고, (컴파일된 정의와 연결된 namespace에서) 각 id의 최상위 매핑을 동시에 그 바인딩으로 설정합니다.
예시:
(define-values () (values))
(define-values (x y z) (values 1 2 3))
z
; 3
모듈 본문의 함수 정의에 대한 define-values 폼이 참인 값의 'compiler-hint:cross-module-inline syntax property를 가지면, Racket은 그 속성을 성능 힌트로 취급합니다. 자세한 내용은 The Racket Guide의 Function-Call Optimizations를 참고하고, begin-encourage-inline도 참고하세요.
(define-syntax id expr)(define-syntax (head args) body ...+)
첫 번째 폼은 expr의 값으로 id의 transformer 바인딩을 만듭니다 (〔Transformer Bindings〕 참고). 여기서 expr은 주변 문맥에 대해 phase level 1인 표현식이에요. (phase levels에 대한 정보는 Identifiers, Binding, and Scopes 참고.) expr의 평가는 let-syntax처럼 current-namespace를 설정하도록 parameterize됩니다.
두 번째 폼은 define과 같은 축약형이에요. expr이 lambda 폼인 첫 번째 폼의 정의로 확장됩니다.
내부 정의 문맥(〔Internal Definitions〕 참고)에서는 define-syntax 폼이 지역 바인딩을 도입합니다.
예시:
(define-syntax foo
(syntax-rules ()
((_ a ...)
(printf "~a\n" (list a ...)))))
(foo 1 2 3 4)
; (1 2 3 4)
(define-syntax (bar syntax-object)
(syntax-case syntax-object ()
((_ a ...)
#'(printf "~a\n" (list a ...)))))
(bar 1 2 3 4)
; (1 2 3 4)
(define-syntaxes (id ...) expr)
define-syntax와 같지만, 각 id에 대해 transformer 바인딩을 만듭니다. expr은 id의 개수만큼의 값을 만들어야 하고, 각 값은 대응하는 id에 바인딩됩니다.
최상위 define-syntaxes(모듈이나 내부 정의 위치가 아닌 경우)에 대해 expr이 0개의 값을 만들면, id들은 사실상 바인딩 없이 선언됩니다 (Macro-Introduced Bindings 참고).
내부 정의 문맥(〔Internal Definitions〕 참고)에서는 define-syntaxes 폼이 지역 바인딩을 도입합니다.
예시:
(define-syntaxes (foo1 foo2 foo3)
(let ([transformer1 (lambda (syntax-object)
(syntax-case syntax-object ()
((_) #'1)))]
[transformer2 (lambda (syntax-object)
(syntax-case syntax-object ()
((_) #'2)))]
[transformer3 (lambda (syntax-object)
(syntax-case syntax-object ()
((_) #'3)))])
(values transformer1
transformer2
transformer3)))
(foo1)
; 1
(foo2)
; 2
(foo3)
; 3
(define-for-syntax id expr)(define-for-syntax (head args) body ...+)
define과 같지만, 바인딩이 그 문맥에 대해 phase level 0이 아니라 phase level 1에 있어요. 바인딩을 위한 표현식도 phase level 1입니다. (phase levels에 대한 정보는 Identifiers, Binding, and Scopes 참고.) 이 폼은 (begin-for-syntax (define id expr)) 또는 (begin-for-syntax (define (head args) body ...+))의 축약형이에요.
모듈 안에서 define-for-syntax가 도입하는 바인딩은 사용처보다 앞에 나타나거나 같은 define-for-syntax 폼 안에 있어야 해요(즉, define-for-syntax 폼이 사용처가 확장되기 전에 확장되어야 함). 특히 define-for-syntax로 바인딩된 상호 재귀 함수는 같은 define-for-syntax 폼으로 정의되어야 합니다.
예시:
(define-for-syntax helper 2)
(define-syntax (make-two syntax-object)
(printf "helper is ~a\n" helper)
#'2)
(make-two)
; helper is 2
; 2
; 'helper' is not bound in the runtime phase
helper
; helper: undefined;
; cannot reference an identifier before its definition
; in module: top-level
(define-for-syntax (filter-ids ids)
(filter identifier? ids))
(define-syntax (show-variables syntax-object)
(syntax-case syntax-object ()
((_ expr ...)
(with-syntax ([(only-ids ...)
(filter-ids (syntax->list #'(expr ...)))])
#'(list only-ids ...)))))
(let ([a 1] [b 2] [c 3])
(show-variables a 5 2 b c))
; '(1 2 3)
(define-values-for-syntax (id ...) expr)
define-for-syntax와 같지만, expr은 제공된 id의 개수만큼의 값을 만들어야 하고, 모든 id가 (phase level 1에서) 바인딩됩니다.
예시:
(define-values-for-syntax (foo1 foo2) (values 1 2))
(define-syntax (bar syntax-object)
(printf "foo1 is ~a foo2 is ~a\n" foo1 foo2)
#'2)
(bar)
; foo1 is 1 foo2 is 2
; 2
3.14.1 require 매크로
(require racket/require-syntax)
| package: base |
이 절에서 문서화한 바인딩은 racket/base나 racket이 아니라 racket/require-syntax 라이브러리가 제공합니다.
(define-require-syntax id proc-expr)(define-require-syntax (id args ...) body ...+)
첫 번째 폼은 define-syntax와 같지만, require 하위 폼을 위한 것이에요. proc-expr은 require 하위 폼을 나타내는 syntax object를 받고 반환하는 프로시저를 만들어야 합니다.
이 폼은 make-require-transformer를 사용하는 define-syntax로 확장됩니다 (자세한 내용은 require Transformers 참고).
두 번째 폼은 define-syntax와 같은 축약형이에요. proc-expr이 lambda 폼인 첫 번째 폼의 정의로 확장됩니다.
(syntax-local-require-introduce stx) → syntax?stx : syntax?
하위 호환성을 위해서만 존재하며, syntax-local-introduce와 동등해요.
버전 6.90.0.29의 package base에서 변경됨: syntax-local-introduce와 동등하게 만듦.
3.14.2 provide 매크로
(require racket/provide-syntax)
| package: base |
이 절에서 문서화한 바인딩은 racket/base나 racket이 아니라 racket/provide-syntax 라이브러리가 제공합니다.
(define-provide-syntax id proc-expr)(define-provide-syntax (id args ...) body ...+)
첫 번째 폼은 define-syntax와 같지만, provide 하위 폼을 위한 것이에요. proc-expr은 provide 하위 폼을 나타내는 syntax object를 받고 반환하는 프로시저를 만들어야 합니다.
이 폼은 make-provide-transformer를 사용하는 define-syntax로 확장됩니다 (자세한 내용은 provide Transformers 참고).
두 번째 폼은 define-syntax와 같은 축약형이에요. expr이 lambda 폼인 첫 번째 폼의 정의로 확장됩니다.
(syntax-local-provide-introduce stx) → syntax?stx : syntax?
하위 호환성을 위해서만 존재하며, syntax-local-introduce와 동등해요.
버전 6.90.0.29의 package base에서 변경됨: syntax-local-introduce와 동등하게 만듦.