2007년 12월 12일 수요일

Packages (패키지)

1. 개념

패키지란 심볼들을 모아놓은 것이다. 리습에서 심볼이란 그저 변수 또는 함수의 '이름'이라고 생각하면 무방하다. 여러 명이 같이 코딩을 할 때 변수의 이름으로 같은 심볼을 사용한다면 이름 충돌이 생길 수 있다. 패키지는 이름 공간(name space)의 분리를 통해 이와 같은 충돌을 막는다. 다음 코드를 보자.

? (make-package :bob)
#
? (make-package :jane)
#
? (in-package bob)
#
? (defun foo () "This is Bob's foo")
FOO
? (in-package jane)
#
? (defun foo () "This is Jane's foo")
FOO
? (foo)
"This is Jane's foo"
? (in-package bob)
#
? (foo)
"This is Bob's foo"

밥과 제인은 bob과 jane이라는 각자의 이름 공간을 만들고, 각자의 이름 공간 안에서 foo라는 함수를 정의하였다. (in-package bob) 뒤에 오는 코드들은 bob 패키지의 이름 공간 안에서 읽히게 된다. 밥이 제인의 foo를 호출하려면 다음과 같이 하면 된다.

? (in-package bob)
#
? (jane::foo)
"This is Jane's foo"

또는 import를 사용해서 :: 없이 심볼을 참조할 수도 있다.

? (in-package jane)
#
? (defun baz () "This is Jane's baz")
BAZ
? (in-package bob)
#
? (import 'jane::baz)
T
? (baz)
"This is Jane's baz"

심볼을 특정한 패키지로 집어넣는 것을 intern 시킨다고 한다. 하지만 intern이라는 함수는 없고, intern시키기 위해서는 위에서와 같이 import를 사용해야 한다. 어느 패키지에도 속하지 않는 심볼을 uninterned 되었다고 하는데, uninterned된 심볼은 평가되면 앞에 #: 이 붙는다(사실 이는 정확한 설명이 아닌데, 정확하게는 홈 패키지가 없는 심볼에 #:이 붙는다). 다음 예를 보면, 어떤 패키지에도 속하지 않은 symbol1을 평가하면 앞에 #:이 붙지만 symbol1을 import 하면(common-lisp-user 패키지로 intern 시킨 것이다) #:이 사라지는 것을 볼 수 있다.

? symbol1
#:MY-SYMBOL
? (import symbol1)
T
? symbol1

import의 반대는 뭘까? 이름에 일관성이 없지만, unintern이다. 어떤 심볼을 패키지에서 제거하기 위해 unintern을 사용한다.

그리고, Home Package란 개념이 있다. A라는 심볼이 있는데 그 심볼이 처음으로 만들어진 패키지가 B라고 하자. 그리고 C라는 패키지가 A라는 심볼을 B에서 import 했다고 하자. 이럴 경우 A가 B에서 처음 만들어졌기 때문에 B가 A의 home 패키지가 된다. 이 경우에 D라는 패키지에서 A를 참조하려고 한다면 다음과 같이 할 수 있다.

B:A 또는 C::A

즉, 홈 패키지의 심볼을 참조할 때는 콜론(:)을 하나만 붙여도 된다.

2. 실제로 알아야 하는 것

실제로 프로그램을 짤 때는 intern 등을 직접 사용하기보다 defpackage를 사용한다. defpackage는 패키지를 정의하면서 그 패키지가 어떤 패키지를 사용(다른 패키지를 사용한다는 것은 그 패키지의 심볼들을 참조할 수 있다는 것이다)하며 그 패키지의 어떤 심볼들을 외부에서 사용할 수 있게 허용할 것인지를 정의한다. 다음을 보자.

(defpackage :com.gigamonkeys.text-db
(:use :common-lisp)
(:export :open-db
:save
:store))

이 코드는 com.gigamonkeys.text-db 라는 패키지를 정의하고 있는데, 이 패키지는 common-lisp이라는 패키지를 사용하며(따라서 그 패키지 안에 있는 모든 심볼들을 : 없이 접근 가능하다. common-lisp 패키지는 커먼 리습 언어의 모든 기본적인 심볼들을 가지고 있는 패키지이다) open-db, save, store라는 패키지 내의 심볼들을 외부(즉, com.gigamonkeys.text-db라는 패키지를 사용한다고 선언한 패키지들)에서 사용할 수 있게 제공한다.

그리고 다음 코드를 보자.

(defpackage :com.gigamonkeys.email-db
(:use
:common-lisp
:com.gigamonkeys.text-db
:com.acme.text)
(:import-from :com.acme.email :parse-email-address)
(:shadow :build-index)
(:shadowing-import-from :com.gigamonkeys.text-db :save))

:import-from은 com.acme.email 패키지 전체를 사용하는 것이 아니라 그 패키지의 parse-email-address 심볼만 가져오겠다는 것이다. 반대로 :shadow는 build-index라는 심볼만 사용에서 제외시키고 싶을 때 사용한다. 그리고 (:shadowing-import-from :com.gigamonkeys.text-db :save) 는 save라는 심볼이 com.gigamonkeys.text-db에도 있고 com.acme.text에도 있을 때 두 패키지 중 com.acme.text의 save가 아니라com.gigamonkeys.text-db의 save 심볼을 가져오겠다는 것이다.

do-symbols, do-external-symbols 등은 특정 패키지 안의 심볼들 전부나, 특정 패키지 안에서 export한다고 선언한 심볼들 전체를 이터레이트하면서 무언가를 하도록 선언하는 매크로이다. 자세한 것은 Common Lisp HyperSpec을 참조하길 바란다.

References
Ron Garret의 홈페이지 - The Idiot's Guide to Common Lisp Packages
http://www.flownet.com/ron/
Practical Common Lisp 21장 - Programming in the Large: Packages and Symbols
http://www.gigamonkeys.com/book/programming-in-the-large-packages-and-symbols.html
Common Lisp HyperSpec
http://www.lispworks.com/documentation/HyperSpec/Body/m_do_sym.htm

댓글 없음: