WITH-PACKAGE-ITERATOR — 패키지 심볼 순회 매크로

WITH-PACKAGE-ITERATOR — 패키지 심볼 순회 매크로

패키지 안에 들어 있는 심볼들을 하나씩 훑어야 할 때가 있어요. do-symbols 같은 반복문도 있지만, 루프 밖에서 '심볼 하나를 꺼내 주는 함수'로 만들어 두면 흐름을 훨씬 유연하게 제어할 수 있죠. 그럴 때 쓰는 것이 with-package-iterator예요. 이 문서는 매크로(macro) 문서예요.

출처: CLHS WITH-PACKAGE-ITERATOR

본문

문법 (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 심볼 중 하나예요.
  • declarationdeclare 표현식이에요. 평가되지 않아요.
  • forms — implicit progn이에요.
  • resultsforms의 값들이에요.

설명 (Description)

본문 forms의 어휘적 유효 범위 안에서 namemacrolet으로 정의돼서, (name)을 호출할 때마다 package-list에 있는 패키지들에서 심볼을 하나씩 돌려줘요.

여러 패키지에서 상속받은 심볼이 여러 번 반환될지는 명세에 정의되어 있지 않아요. 반환 순서도 반드시 package-list의 패키지 순서를 따를 필요가 없어요. package-list에 요소가 둘 이상이면 중복 심볼이 한 번 반환될지 여러 번 반환될지 역시 명세에 없어요.

symbol-types는 어떤 심볼을 반환할지 정해요. 패키지에서 접근 가능한(accessible) 심볼들을 대상으로요.

  • :internal — 패키지에 present하고 exported되지 않은 심볼
  • :external — 패키지에 present하고 exported된 심볼
  • :inherited — 사용 중인(used) 패키지가 export하고 shadow되지 않은 심볼

symbol-types 인자가 여러 개면, 접근성(accessibility)이 그중 어떤 하나라도 맞으면 그 심볼이 반환돼요. 구현이 추가적인 심볼 접근성 타입을 인식해 문법을 확장할 수도 있어요.

(name) 호출은 네 개의 값을 반환해요.

  1. 심볼이 반환됐는지 여부를 나타내는 플래그 (true면 심볼이 반환됨)
  2. 표시된 패키지 중 하나에서 접근 가능한 심볼
  3. 그 심볼의 접근성 타입 — :internal, :external, :inherited 중 하나
  4. 심볼을 얻은 패키지. 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)

없음.