DEFPACKAGE — 패키지 정의하기

DEFPACKAGE — 패키지 정의하기 (매크로)

Common Lisp의 package를 선언적으로 정의하는 매크로예요. 닉네임, 상속(:use), 심볼 노출(:export), 이름 충돌 처리(:shadow 등) 등을 한 곳에서 지정해서 심볼 네임스페이스를 깔끔하게 관리해요.

출처: DEFPACKAGE - Common Lisp HyperSpec

시그니처 (Syntax)

defpackage defined-package-name [[option]]* => package
option ::= (:nicknames nickname*)* |
          (:documentation string) |
          (:use package-name*)* |
          (:shadow {symbol-name}*)* |
          (:shadowing-import-from package-name {symbol-name}*)* |
          (:import-from package-name {symbol-name}*)* |
          (:export {symbol-name}*)* |
          (:intern {symbol-name}*)* |
          (:size integer)
  • defined-package-namestring designator.
  • package-namepackage designator.
  • nicknamestring designator.
  • symbol-namestring designator.
  • packagepackage-name이 가리키는 package.

본문 (Description)

defpackage는 지정된 대로 package를 만들고 그 package를 돌려줘요.

defined-package-name이 이미 존재하는 package를 가리키면 그 이름에 대한 이름-패키지 매핑은 바뀌지 않아요. 새 정의가 그 package의 현재 상태와 어긋나면 결과는 정의되지 않아요. 구현이 기존 package를 새 정의를 반영하도록 수정할 수도 있어요. defined-package-namesymbol이면 그 name이 쓰여요.

표준 option들은 아래와 같아요.

:nicknames

:nicknames의 인자는 그 package의 닉네임을 제공된 이름들로 설정해요.

:documentation

:documentation의 인자는 documentation string을 지정하는데, 그 packagedocumentation string으로 붙어요. 하나의 defpackage form에는 :documentation 옵션이 최대 하나만 나타날 수 있어요.

:use

:use의 인자는 package-name이 가리키는 package가 상속받을 package들을 설정해요. :use가 제공되지 않으면 make-package의 :use argument와 같은 implementation-dependent 값을 기본으로 해요.

:shadow

:shadow의 인자인 symbol-name들은 정의되는 package 안에 만들 symbol들을 가리켜요. 이 symbol들은 마치 shadow에 의한 것처럼 shadowing symbol 목록에 추가돼요.

:shadowing-import-from

인자 symbol-name들이 가리키는 symbol들은 (마치 find-symbol처럼 조회를 통해) 지정된 package-name에서 찾아져요. 결과 symbol들은 정의되는 packageimport되고, shadowing-import처럼 shadowing symbols 목록에 올라가요. 어떤 경우에도 symbol은 정의되는 package 외의 다른 package에는 만들어지지 않아요.

:import-from

인자 symbol-name들이 가리키는 symbol들은 package-name이 가리키는 package에서 찾아져서 정의되는 packageimport돼요. 어떤 경우에도 symbol은 정의되는 package 외의 다른 package에는 만들어지지 않아요.

:export

인자 symbol-name들이 가리키는 symbol들은 정의되는 package에서 찾거나 만들어져서 export돼요. :export 옵션은 :use 옵션과 상호작용하는데, 새로 만들지 않고 상속된 symbol을 쓸 수 있으니까요. :export 옵션은 :import-from·:shadowing-import-from 옵션과도 상호작용하는데, 새로 만들지 않고 importedsymbol을 쓸 수 있어요. :export 옵션의 인자가 use-package를 통해 (상속된) internal symbolaccessible하면, symbol-name이 가리키는 symbol이 먼저 정의되는 packageimport되고 나서 그 package에서 export돼요.

:intern

인자 symbol-name들이 가리키는 symbol들은 정의되는 package에서 찾거나 만들어져요. :intern 옵션은 :use 옵션과 상호작용하는데, 새로 만들지 않고 상속된 symbol을 쓸 수 있으니까요.

:size

:size 옵션의 인자는 그 package에서 기대되는 symbol의 대략적 개수를 선언해요. 이것은 효율성 힌트일 뿐이며 구현이 무시할 수 있어요.

옵션 실행 순서

defpackage form에서 옵션이 나타나는 순서는 무관해요. 실행 순서는 다음과 같아요:

  1. :shadow와 :shadowing-import-from. 2. :use. 3. :import-from와 :intern. 4. :export.

Shadow는 먼저 확립돼요. :use 옵션을 처리할 때 가짜 이름 충돌을 막는 데 필요할 수 있으니까요. :use 옵션은 다음에 실행되어 :intern·:export 옵션이 정상적으로 상속된 symbol을 참조할 수 있게 해요. :export 옵션은 마지막에 실행되어 다른 옵션들이 만든 symbol을 참조할 수 있게 해요. 특히 shadowing symbolimported symbol을 external로 만들 수 있어요.

defpackage formtop level form으로 나타나면, 이 macro가 로드 시점에 보통 수행하는 모든 동작을 컴파일 시점에도 수행해야 해요.

예제 (Examples)

(defpackage "MY-PACKAGE"
  (:nicknames "MYPKG" "MY-PKG")
  (:use "COMMON-LISP")
  (:shadow "CAR" "CDR")
  (:shadowing-import-from "VENDOR-COMMON-LISP"  "CONS")
  (:import-from "VENDOR-COMMON-LISP"  "GC")
  (:export "EQ" "CONS" "FROBOLA")
  )

(defpackage my-package
  (:nicknames mypkg :MY-PKG)  ; remember Common Lisp conventions for case
  (:use common-lisp)          ; conversion on symbols
  (:shadow CAR :cdr #:cons)
  (:export "CONS")            ; this is the shadowed one.
  )

예외 상황 (Exceptional Situations)

제공된 :nicknames 중 하나가 이미 존재하는 package를 가리키면 typepackage-error인 에러가 신호돼요.

:size나 :documentation이 두 번 이상 나타나면 typeprogram-error인 에러가 신호되어야 해요.

구현이 확장 option을 허용할 수 있으므로, 호스트 implementation에서 실제로 지원되지 않는 option이 있으면 typeprogram-error인 에러가 신호되어야 해요.

:shadow·:intern·:import-from·:shadowing-import-from 옵션에 주어진 symbol-name 인자들의 모음은 모두 서로 겹치지 않아야 해요. 게다가 :export와 :intern에 주어진 symbol-name 인자들도 겹치지 않아야 해요. 이 맥락에서 disjoint는 두 symbol-name이 서로 **string=**인 일이 없다는 뜻이에요. 어느 조건이라도 위반되면 typeprogram-error인 에러가 신호되어야 해요.

:shadowing-import-from·:import-from 옵션에서, 인자 symbol-name 중 하나에 대해 package-name이 가리키는 packageaccessiblesymbol이 없으면 typepackage-errorcorrectable error가 신호돼요.

이름 충돌 에러는 뒤에 호출되는 make-package, use-package, import, export가 처리해요. Section 11.1 (Package Concepts)를 보세요.

더 알아보기 (See Also)

documentation, Section 11.1 (Package Concepts), Section 3.2 (Compilation)

Notes

:intern 옵션은, (다른 package에 대한) 이후 defpackage 호출의 :import-from이나 :shadowing-import-from 옵션이 이 symbol들을 external은 아니지만 accessible하길 기대할 때 유용해요.

package 정의 전체를 한 곳에 두고, 프로그램의 모든 package 정의를 단일 파일에 두는 것이 권장돼요. 이 파일은 그 package들에 의존하는 다른 무엇이든 load·compile하기 전에 load할 수 있어요. 그런 파일은 COMMON-LISP-USER 패키지에서 읽을 수 있어서 초기 상태 문제를 피할 수 있어요.

defpackage로 서로 "상호 재귀"적인 두 패키지를 만들 수는 없어요. 예:

(defpackage my-package
  (:use common-lisp your-package)    ;requires your-package to exist first
  (:export "MY-FUN"))
(defpackage your-package
  (:use common-lisp)
  (:import-from my-package "MY-FUN") ;requires my-package to exist first
  (:export "MY-FUN"))

다만 use-package, import, export 같은 package-영향 함수를 써서, 표준적인 defpackage 사용 뒤에 그런 연결을 확립하는 것은 막지 않아요.

defpackage의 macroexpansion이 이름을 string으로 정규화하면 유용해요. 원본 파일에 임의의 symbol이 있어도 컴파일된 파일에는 string만 들어 있게 되니까요.

종종 추가 implementation-dependent 옵션은 list (keyword T)의 축약으로 혼자 있는 keyword 형태를 띠는데, 지원하지 않는 구현에서는 이 문법을 인식되지 않은 옵션으로 적절히 보고해야 해요.