유닛 만들기

유닛 만들기

유닛(unit)은 정의와 표현식을 정적으로 묶어 놓은 모음으로, 임포트(import)와 익스포트(export)를 가질 수 있어요. 유닛은 정적으로 묶여 있다는 점에서, 그 임포트들이 익스포트들에 컴파일 시점에 연결돼요.

이 절에서는 유닛을 만드는 핵심 형식인 unit과, 유닛이 주고받는 시그니처를 정의하는 define-signature을 다뤄요.

출처: Racket Reference

본문

syntax unit

(unit
  (import tagged-sig-spec ...)
  (export tagged-sig-spec ...)
  init-depends-decl
  unit-body-expr-or-defn ...)
  • tagged-sig-spec = sig-spec | (tag id sig-spec)
  • sig-spec = sig-id | (prefix id sig-spec) | (rename sig-spec (id id) ...) | (only sig-spec id ...) | (except sig-spec id ...)
  • init-depends-decl = `` | (init-depend tagged-sig-id ...)
  • tagged-sig-id = sig-id | (tag id sig-id)

자신의 unit-body-expr-or-defn들을 감싸는 유닛을 만들어내요. 유닛 본문의 표현식은 임포트 절의 sig-spec들이 바인딩한 식별자들을 참조할 수 있고, 본문은 익스포트 절의 각 sig-spec 식별자에 대한 정의를 정확히 하나씩 포함해야 해요.

익스포트되는 식별자는 정의하는 유닛과 임포트하는 유닛 어디에서도 set!될 수 없어요. 다만 변수를 초기화하기 위한 암묵적 할당은 변경(mutation)으로 보일 수는 있어요.

각 임포트·익스포트 sig-spec은 궁극적으로 하나의 sig-id를 가리키는데, sig-iddefine-signature에 의해 시그니처에 바인딩된 식별자예요. sig-id를 통해 임포트되는 각 식별자의 어휘 정보는 sig-id의 어휘 정보로부터 시작돼요(자세한 내용은 define-signature 형식 참고).

특정 임포트·익스포트 위치에서, 특정 sig-id가 바인딩하거나 요구하는 식별자 집합은 몇 가지 방법으로 조정할 수 있어요:

  • 임포트로서의 (prefix id sig-spec)는 각 바인딩이 id로 접두사가 붙는다는 점만 빼고 sig-spec과 동일하게 바인딩해요. 익스포트로서 이 형식은 id 접두사를 사용하는 정의들이 sig-spec이 요구하는 익스포트를 충족시키게 해요.
  • 임포트로서의 (rename sig-spec (id id) ...)는 두 번째 id 대신 첫 번째 id를 바인딩에 사용한다는 점만 빼고 sig-spec과 동일하게 바인딩해요(여기서 sig-spec 자체가 bound-identifier=?가 두 번째 id인 바인딩을 함의해야 해요). 익스포트로서 이 형식은 첫 번째 id에 대한 정의가 sig-spec에서 두 번째 id가 가리키는 익스포트를 충족시키게 해요.
  • 임포트로서의 (only sig-spec id ...)는 나열된 id들로만 제한된다는 점만 빼고 sig-spec과 동일하게 바인딩해요(여기서 sig-spec 자체가 각 id에 대해 bound-identifier=?인 바인딩을 함의해야 해요). 이 형식은 익스포트에는 허용되지 않아요.
  • 임포트로서의 (except sig-spec id ...)는 나열된 모든 id를 제외한다는 점만 빼고 sig-spec과 동일하게 바인딩해요(여기서 sig-spec 자체가 각 id에 대해 bound-identifier=?인 바인딩을 함의해야 해요). 이 형식은 익스포트에는 허용되지 않아요.

문법이 암시하듯, 시그니처에 대한 이러한 조정들은 임의로 중첩될 수 있어요.

유닛의 선언된 임포트들은 실제로 공급되는 임포트들과 시그니처로 매칭돼요. 즉, 링크할 때 유닛에 임포트가 공급되는 순서는 중요하지 않고, 각 공급된 임포트가 구현하는 시그니처만 중요해요. 각 선언된 임포트마다 하나의 실제 임포트가 제공되어야 해요. 마찬가지로 유닛이 여러 시그니처를 구현할 때 익스포트 시그니처의 순서도 중요하지 않아요.

같은 시그니처에 대해 여러 임포트·익스포트를 지원하기 위해, 임포트·익스포트는 (tag id sig-spec) 형식을 사용해 태그를 달 수 있어요. 유닛의 임포트 선언에 태그가 달리면, 유닛을 링크할 때 실제 임포트 하나에 같은 태그(같은 시그니처)가 주어져야 해요. 마찬가지로 유닛의 익스포트 선언에 태그가 달리면, 그 특정 익스포트에 대한 참조는 반드시 그 태그를 명시적으로 사용해야 해요.

유닛은 문법적으로 서로 구분되지 않는 두 시그니처를 임포트하는 것이 금지돼요(다른 태그를 갖지 않는 한). 두 시그니처는 extends를 통해 공통 조상이 없을 때만 구분돼요. 이 문법적 제약은 익스포트 시그니처에도 똑같이 적용돼요. 게다가 유닛은 문법적으로 같은 식별자를 두 번 임포트하는 것(sig-spec에 대한 리네이밍 및 다른 변환 이후에도), 같은 식별자를 두 번 익스포트하는 것(역시 리네이밍 이후에도), 그리고 임포트된 식별자를 익스포트하는 것이 금지돼요.

유닛들이 링크될 때, 링크된 유닛들의 본문은 링크 위치에서 지정된 순서로 실행돼요. 선택적 (init-depend tagged-sig-id ...) 선언은 현재 유닛이 그에 해당하는 임포트를 공급하는 유닛보다 뒤에 초기화되어야 한다고 지정함으로써 허용되는 링크 순서를 제약해요. init-depend 선언의 각 tagged-sig-id는 임포트 절에 대응하는 임포트가 있어야 해요.

syntax define-signature

(define-signature sig-id extension-decl (sig-elem ...))
  • extension-decl = `` | extends sig-id
  • sig-elem = id | (define-syntaxes (id ...) expr) | (define-values (id ...) expr) | (define-values-for-export (id ...) expr) | (contracted [id contract] ...) | (open sig-spec) | (struct id (field ...) struct-option ...) | (sig-form-id . datum)
  • field = id | [id #:mutable]
  • struct-option = #:mutable | #:constructor-name constructor-id | #:extra-constructor-name constructor-id | #:omit-constructor | #:omit-define-syntaxes | #:omit-define-values

sig-id를 임포트·익스포트용 바인딩 그룹을 지정하는 시그니처에 바인딩해요:

  • 시그니처 선언의 각 id는, 그 시그니처를 구현하는 유닛이 id에 대한 변수 정의를 공급해야 한다는 뜻이에요. 즉 id는 시그니처를 임포트하는 유닛에서 사용할 수 있고, 시그니처를 익스포트하는 유닛이 정의해야 해요.
  • 시그니처 선언의 각 define-syntaxes 형식은 시그니처를 임포트하는 모든 유닛에서 사용할 수 있는 매크로를 도입해요. 정의의 expr에 있는 자유 변수는 시그니처의 다른 식별자를 먼저 참조하고, 시그니처가 그 식별자를 포함하지 않으면 define-signature 형식의 문맥을 참조해요.
  • 시그니처 선언의 각 define-values 형식은 시그니처를 임포트하는 모든 유닛 앞에 효과적으로 붙는 코드를 도입해요. 정의의 expr에 있는 자유 변수는 define-syntaxes와 동일하게 취급돼요.
  • 시그니처 선언의 각 define-values-for-export 형식은 시그니처를 익스포트하는 모든 유닛 뒤에 효과적으로 붙는 코드를 도입해요. 정의의 expr에 있는 자유 변수는 define-syntaxes와 동일하게 취급돼요.
  • 시그니처 선언의 각 contracted 형식은 그 시그니처를 익스포트하는 유닛이 그 형식의 각 id에 대한 변수 정의를 공급해야 한다는 뜻이에요. 시그니처가 임포트되면, 유닛 내부의 id 사용은 해당 계약으로 보호되는데, 이때 유닛이 음(negative) 책임(blame)이 돼요. 시그니처가 익스포트되면, 익스포트된 값들은 계약으로 보호되는데 이때 유닛이 양(positive) 책임이 되고, 익스포트된 식별자의 내부 사용은 보호되지 않아요. 계약 표현식의 변수들은 define-syntaxes와 동일하게 취급돼요.
  • (open sig-spec)sig-spec이 지정하는 모든 것을 시그니처에 추가해요.
  • (struct id (field ...) struct-option ...)struct 형식이 바인딩할 모든 식별자를 추가하는데, 추가 옵션 #:omit-constructor는 생성자 식별자를 생략해요.
  • (sig-form-id . datum)define-signature-form이 바인딩한 sig-form-id가 정의하는 방식으로 시그니처를 확장해요. 그러한 바인딩 중 하나가 struct/ctc예요.

define-signature 형식이 extends 절을 포함하면, 그 정의된 시그니처는 자동으로 확장된 시그니처의 모든 것을 포함해요. 게다가 새 시그니처의 어떤 구현이든 확장된 시그니처의 구현으로 사용될 수 있어요.

시그니처 내 각 id의 어휘 정보는 sig-id의 어휘 정보와 비교돼요. sig-id에 대한 id의 추가 스코프가 id에 기록돼요. sig-id가 (예를 들어 unit의 임포트 절에서) 참조로 사용되면, 참조하는 sig-id의 어휘 정보에서 시작해 id의 추가 스코프를 더해, 참조 문맥용 id 변형이 만들어져요.

syntax open

(open sig-spec)

sig-elem에서만 허용돼요. define-signature 참고.

syntax define-values-for-export

(define-values-for-export (id ...) expr)

sig-elem에서만 허용돼요. define-signature 참고.

syntax contracted

(contracted [id contract] ...)

sig-elem에서만 허용돼요. define-signature 참고.

syntax only

(only sig-spec id ...)

sig-spec에서만 허용돼요. unit 참고.

syntax except

(except sig-spec id ...)

sig-spec에서만 허용돼요. unit 참고.

syntax rename

(rename sig-spec (id id) ...)

sig-spec에서만 허용돼요. unit 참고.

syntax prefix

(prefix id sig-spec)

sig-spec에서만 허용돼요. unit 참고.

syntax import

(import tagged-sig-spec ...)

특정 형식에서만 허용돼요. 예를 들어 unit 참고.

syntax export

(export tagged-sig-spec ...)

특정 형식에서만 허용돼요. 예를 들어 unit 참고.

syntax link

(link linkage-decl ...)

특정 형식에서만 허용돼요. 예를 들어 compound-unit 참고.

syntax tag

(tag id sig-spec)
(tag id sig-id)

특정 형식에서만 허용돼요. 예를 들어 unit 참고.

syntax init-depend

(init-depend tagged-sig-id ...)

init-depends-decl에서만 허용돼요. unit 참고.

syntax extends

define-signature 내에서만 허용돼요.

더 알아보기