WITH-HASH-TABLE-ITERATOR — 해시 테이블을 순회하는 매크로

WITH-HASH-TABLE-ITERATOR — 해시 테이블을 순회하는 매크로

해시 테이블의 항목들을 하나씩 꺼내 순회하고 싶을 때 쓰는 매크로예요. maphash처럼 콜백 방식도 있지만, 여기서는 몸체 안에서 명시적으로 '다음 항목'을 호출하는 방식으로 순회해요.

출처: CLHS: Macro WITH-HASH-TABLE-ITERATOR

시그니처

with-hash-table-iterator (name hash-table) declaration* form* => results*

본문

인자와 값 (Arguments and Values)

  • namemacrolet의 첫 번째 인자로 쓰기에 적합한 이름.
  • hash-table — 폼; 한 번 평가되어 해시 테이블을 만들어야 해요.
  • declarationdeclare 식; 평가되지 않아요.
  • forms — 암묵적 progn.
  • resultsforms가 돌려준 값.

설명 (Description)

몸체의 어휘 범위 안에서, namemacrolet으로 정의해요. 그래서 (name)을 연속해서 호출하면, hash-table을 한 번만 평가해 얻은 해시 테이블에서 항목들을 하나씩 돌려줘요.

(name) 호출은 값 세 개를 돌려줘요:

  1. 항목이 반환되면 true인 일반화된 불(generalized boolean).
  2. 해시 테이블 항목의 키(key).
  3. 해시 테이블 항목의 값(value).

(name)을 계속 호출해 모든 항목을 받은 뒤에는 값 하나, 즉 nil만 돌려줘요.

반복의 암묵적 내부 상태를 with-hash-table-iterator 폼의 동적 지속 기간(dynamic extent) 밖으로 가져가는 경우(예: 호출 폼을 감싼 클로저를 돌려주는 경우) 어떻게 될지는 명시되지 않아요.

with-hash-table-iterator 호출은 몇 번이든 중첩될 수 있고, 가장 안쪽 몸체는 로컬로 설치된 모든 매크로를 호출할 수 있어요. 단, 그 매크로들이 모두 서로 다른 이름을 가져야 해요.

예제 (Examples)

다음 함수는 어떤 해시 테이블에서도 t를 돌려줘야 하고, with-hash-table-iterator의 사용이 대응하는 maphash 사용과 맞지 않으면 오류를 신호해요:

 (defun test-hash-table-iterator (hash-table)
   (let ((all-entries '())
         (generated-entries '())
         (unique (list nil)))
     (maphash #'(lambda (key value) (push (list key value) all-entries))
              hash-table)
     (with-hash-table-iterator (generator-fn hash-table)
       (loop
         (multiple-value-bind (more? key value) (generator-fn)
           (unless more? (return))
           (unless (eql value (gethash key hash-table unique))
             (error "Key ~S not found for value ~S" key value))
           (push (list key value) generated-entries))))
     (unless (= (length all-entries)
                (length generated-entries)
                (length (union all-entries generated-entries
                               :key #'car :test (hash-table-test hash-table))))
       (error "Generated entries and Maphash entries don't correspond"))
     t))

다음은 with-hash-table-iterator로 구현한, 받아들일 만한 maphash 정의가 될 수 있어요:

 (defun maphash (function hash-table)
   (with-hash-table-iterator (next-entry hash-table)
     (loop (multiple-value-bind (more key value) (next-entry)
             (unless more (return nil))
             (funcall function key value)))))

부수 효과 (Side Effects)

없음.

특이 상황 (Exceptional Situations)

with-hash-table-iterator가 설치한 로컬 함수 name이 일차 값으로 false를 돌려준 뒤에 호출되면 결과는 정의되지 않아요(undefined).

참고 (Notes)

없음.

더 알아보기

  • Section 3.6 — 순회 규칙과 부수 효과