do-symbols / do-external-symbols / do-all-symbols — 패키지의 심볼 순회

do-symbols / do-external-symbols / do-all-symbols — 패키지의 심볼 순회

패키지에 어떤 심볼이 들어 있는지 하나씩 훑어봐야 할 때가 있어요. 패키지에서 심볼을 찾는 건 find-symbol로 가능하지만, "패키지 전체에 이 이름이 있나?" 같은 조사는 심볼 전체를 순회해야 풀리는 문제예요. do-symbols 계열 매크로는 패키지 안의 심볼들을 반복 처리하는 루프예요.

출처: CLHS 공식 문서 - DO-SYMBOLS, DO-EXTERNAL-SYMBOLS, DO-ALL-SYMBOLS

본문

세 매크로 모두 패키지의 심볼들을 순회하며, 각 심볼을 var에 묶어 본문을 실행해요. 모든 심볼을 처리한 뒤에는 result-form을 평가해 그 값을 반환해요.

do-symbols (var [package [result-form]]) declaration* {tag | statement}*
=> result*

do-external-symbols (var [package [result-form]]) declaration* {tag | statement}*
=> result*

do-all-symbols (var [result-form]) declaration* {tag | statement}*
=> result*

셋의 차이는 순회 대상을 어디에서 고르느냐예요.

  • do-symbolspackage에서 접근 가능한(accessible) 모든 심볼을 순회해요. 여러 패키지에서 상속된 심볼은 두 번 이상 실행될 수 있어요.
  • do-external-symbolspackage외부(external) 심볼만 순회해요.
  • do-all-symbols — 등록된 모든 패키지를 순회해요. 어느 패키지에서도 접근 불가능한 심볼은 처리되지 않고, 여러 패키지에 걸쳐 있는 심볼은 여러 번 처리될 수 있어요.

package 인자는 do-symbolsdo-external-symbols에서 생략할 수 있고, 생략하면 현재 패키지가 기본값이에요. result-form을 평가할 때는 varnil로 묶여 있어요.

(make-package 'temp :use nil)  =>  #<PACKAGE "TEMP">
(intern "SHY" 'temp)           =>  TEMP::SHY, NIL  ; 내부 심볼
(export (intern "BOLD" 'temp) 'temp)  =>  T       ; 외부 심볼

(let ((lst ()))
  (do-symbols (s (find-package 'temp)) (push s lst))
  lst)
=>  (TEMP::SHY TEMP:BOLD)
OR=>  (TEMP:BOLD TEMP::SHY)

(let ((lst ()))
  (do-external-symbols (s (find-package 'temp) lst) (push s lst))
  lst)
=>  (TEMP:BOLD)

do-external-symbols는 외부 심볼 BOLD만 돌려주는 걸 볼 수 있어요. 순회 집합에 드는 심볼의 개수가 달라지면 정의되지 않은 동작이 되니, 반복 도중 본문에서 패키지의 심볼 집합을 바꾸는 작업은 피하는 게 좋아요(unintern으로 현재 값인 심볼을 지우는 경우는 예외).

전체 do-symbols / do-external-symbols / do-all-symbols 폼을 nil이라는 이름의 암묵적 블록이 감싸고 있어서, return이나 return-from으로 중간에 순회를 끊을 수 있어요.

함께 보기

intern, export