WITH-PACKAGE-ITERATOR — 패키지 심볼 순회 매크로
WITH-PACKAGE-ITERATOR — 패키지 심볼 순회 매크로
패키지 안에 들어 있는 심볼들을 하나씩 훑어야 할 때가 있어요. do-symbols 같은 반복문도 있지만, 루프 밖에서 '심볼 하나를 꺼내 주는 함수'로 만들어 두면 흐름을 훨씬 유연하게 제어할 수 있죠. 그럴 때 쓰는 것이 with-package-iterator예요. 이 문서는 매크로(macro) 문서예요.
본문
문법 (Syntax)
with-package-iterator (name package-list-form &rest symbol-types) declaration* form*
=> result*
인자와 값 (Arguments and Values)
name— 심볼(symbol)이에요.package-list-form— form이에요. 한 번 평가되어package-list를 만들어요.package-list— package designator들의 리스트에 대한 designator예요.symbol-type—:internal,:external,:inherited심볼 중 하나예요.declaration—declare표현식이에요. 평가되지 않아요.forms— implicit progn이에요.results—forms의 값들이에요.
설명 (Description)
본문 forms의 어휘적 유효 범위 안에서 name은 macrolet으로 정의돼서, (name)을 호출할 때마다 package-list에 있는 패키지들에서 심볼을 하나씩 돌려줘요.
여러 패키지에서 상속받은 심볼이 여러 번 반환될지는 명세에 정의되어 있지 않아요. 반환 순서도 반드시 package-list의 패키지 순서를 따를 필요가 없어요. package-list에 요소가 둘 이상이면 중복 심볼이 한 번 반환될지 여러 번 반환될지 역시 명세에 없어요.
symbol-types는 어떤 심볼을 반환할지 정해요. 패키지에서 접근 가능한(accessible) 심볼들을 대상으로요.
:internal— 패키지에 present하고 exported되지 않은 심볼:external— 패키지에 present하고 exported된 심볼:inherited— 사용 중인(used) 패키지가 export하고 shadow되지 않은 심볼
symbol-types 인자가 여러 개면, 접근성(accessibility)이 그중 어떤 하나라도 맞으면 그 심볼이 반환돼요. 구현이 추가적인 심볼 접근성 타입을 인식해 문법을 확장할 수도 있어요.
(name) 호출은 네 개의 값을 반환해요.
- 심볼이 반환됐는지 여부를 나타내는 플래그 (true면 심볼이 반환됨)
- 표시된 패키지 중 하나에서 접근 가능한 심볼
- 그 심볼의 접근성 타입 —
:internal,:external,:inherited중 하나 - 심볼을 얻은 패키지.
package-list에 있거나 이름이 언급된 패키지 중 하나예요.
모든 심볼을 반환한 뒤에는 값이 하나만 반환되는데, 바로 nil이에요.
2, 3, 4번 값의 의미를 다시 풀면, 반환된 심볼은 반환된 패키지 안에서 3번 값이 가리키는 방식으로 접근 가능하다는 뜻이에요.
:internal— present하고 not exported:external— present하고 exported:inherited— present하지 않고(따라서 shadow되지 않고) 어떤 used 패키지에서 상속됨
반복의 내부 상태가 with-package-iterator 폼의 dynamic extent 밖으로 나가면 어떻게 될지는 명세에 정의되어 있지 않아요. 예를 들어 호출 폼을 감싸는 클로저를 밖으로 반환하는 경우가 대표적이죠.
with-package-iterator는 몇 겹으로든 중첩할 수 있고, 가장 안쪽 본문에서 지역적으로 확립된 매크로를 모두 호출할 수 있어요. 단, 그 매크로들의 이름이 서로 모두 달라야 해요.
예제 (Examples)
다음 함수는 어떤 패키지에 대해서도 t를 반환하고, with-package-iterator의 사용이 do-symbols의 사용과 맞지 않으면 에러를 신호해요.
(defun test-package-iterator (package)
(unless (packagep package)
(setq package (find-package package)))
(let ((all-entries '())
(generated-entries '()))
(do-symbols (x package)
(multiple-value-bind (symbol accessibility)
(find-symbol (symbol-name x) package)
(push (list symbol accessibility) all-entries)))
(with-package-iterator (generator-fn package
:internal :external :inherited)
(loop
(multiple-value-bind (more? symbol accessibility pkg)
(generator-fn)
(unless more? (return))
(let ((l (multiple-value-list (find-symbol (symbol-name symbol)
package))))
(unless (equal l (list symbol accessibility))
(error "Symbol ~S not found as ~S in package ~A [~S]"
symbol accessibility (package-name package) l))
(push l generated-entries)))))
(unless (and (subsetp all-entries generated-entries :test #'equal)
(subsetp generated-entries all-entries :test #'equal))
(error "Generated entries and Do-Symbols entries don't correspond"))
t))
다음 함수는 present한 심볼을 모두(여러 번일 수 있어요) 출력해요.
(defun print-all-symbols ()
(with-package-iterator (next-symbol (list-all-packages)
:internal :external)
(loop
(multiple-value-bind (more? symbol) (next-symbol)
(if more?
(print symbol)
(return))))))
부수 효과 (Side Effects)
없음.
영향받는 요소 (Affected By)
없음.
예외 상황 (Exceptional Situations)
symbol-types가 하나도 주어지지 않거나 구현이 인식하지 못하는 symbol-type이 주어지면 program-error 타입의 에러를 신호해요.
name으로 확립된 지역 함수가 primary value로 false를 반환한 뒤 호출되는 결과는 정의되어 있지 않아요.
더 알아보기 (See Also)
- Section 3.6 (Traversal Rules and Side Effects)
참고 (Notes)
없음.