Clojure 메타데이터
Clojure 메타데이터 (Metadata)
Clojure의 심볼(symbol)과 컬렉션(collection)에는 메타데이터(metadata), 즉 그 대상을 설명하는 데이터 맵을 달아둘 수 있어요. 이 메타데이터 체계는 데이터에 임의의 주석을 달 수 있게 해줘요. 컴파일러에 타입 정보를 전달하는 데 쓰이기도 하지만, 애플리케이션 개발자가 데이터 소스나 정책 같은 것을 주석으로 표시하는 등 다양한 목적으로 쓸 수 있어요.
출처: Clojure 공식문서
본문
메타데이터는 값의 일부가 아니에요
메타데이터를 이해할 때 가장 중요한 점은, 메타데이터가 객체의 값(value)에 포함되지 않는다는 거예요. 그래서 메타데이터는 동등성(equality, 혹은 해시 코드)에 영향을 주지 않아요. 오직 메타데이터만 다른 두 객체는 동등하게 취급돼요.
다만 메타데이터와 객체의 관계 자체는 불변(immutable) 이에요. 메타데이터가 다른 객체는 곧 다른 객체죠. 여기서 따라오는 한 가지 결과가 있어요. 지연 시퀀스(lazy sequence)에 메타데이터를 적용하면 그 시퀀스의 머리(head)가 실현(realize)돼요. 그래야 두 객체가 같은 시퀀스를 공유할 수 있기 때문이에요.
메타데이터 읽기와 쓰기
메타데이터는 아래 함수들로 읽고 쓰고 바꿀 수 있어요.
(meta obj)
obj의 메타데이터를 돌려줘요. 메타데이터가 없으면 nil을 돌려줘요.
(pprint (meta #'+)) ;; #'+ 는 + var 입니다
;; {:added "1.2",
;; :name +,
;; :file "clojure/core.clj",
;; :column 1,
;; :line 984,
;; :arglists ([] [x] [x y] [x y & more]),
;; ...
(with-meta obj map)
obj와 같은 타입, 같은 값을 가지되 메타데이터가 map인 객체를 돌려줘요. 예시로 확인해 볼게요.
(def m ^:hi [1 2 3])
(meta (with-meta m {:bye true}))
;; {:bye true}
*print-meta*
이 플래그를 논리 참(logical true)으로 설정하면, 객체를 출력할 때 그 메타데이터도 리더(reader)가 다시 읽어들일 수 있는 형태로 함께 출력돼요.
(def m ^:hi [1 2 3])
(binding [*print-meta* true]
(prn m))
;; ^{:hi true} [1 2 3]
(vary-meta obj f & args)
obj와 같되, 메타데이터가 (apply f (meta obj) args)인 객체를 돌려줘요. 기존 메타데이터에 함수를 적용해 새 메타데이터를 만드는 방식이에요.
(def m ^:hi [1 2 3])
(meta (vary-meta m merge {:bye true}))
;; {:hi true, :bye true}
(alter-meta! ref f & args) 와 (reset-meta! ref map)
이 두 함수는 각각 네임스페이스/var/ref/agent/atom의 메타데이터를 수정하거나 초기화해요. alter-meta!는 함수로 바꾸고, reset-meta!는 아예 새 맵으로 설정하죠.
메타데이터 리더 (Metadata Reader)
with-meta 외에도, 읽기 시점(read-time) 에 그 뒤에 오는 표현식에 메타데이터를 적용해주는 리더 지원이 있어요. 이건 리더가 메타데이터 문법을 만나면 그대로 메타데이터를 붙여주는 방식이에요.
컴파일러가 메서드 오버로드(method overload)를 선택할 때 메타데이터를 어떻게 쓰는지는 Type Hints(타입 힌트) 와 param-tags 문서를 보면 더 자세히 알 수 있어요.
더 알아보기
- Metadata Reader (리더 메타데이터) — 리더 문법으로 메타데이터를 적용하는 방법
- Type Hints (타입 힌트) — 컴파일러에 타입 정보를 전달하는 메타데이터 활용
- param-tags — 오버로드 선택을 위한 파라미터 태그