2011-10-13 14 views
21

Próbuję stworzyć małą Clojure makro, które def sa String z nutą typu:Clojure defmacro traci metadanych

(defmacro def-string [name value] 
    `(def ^String ~name ~value)) 

(def-string db-host-option "db-host") 

Kiedy macroexpand go wskazówkę typ jest przegrane:

(macroexpand '(def-string db-host-option "db-host")) 
;=> (def db-host-option "db-host") 

Nie zwracaj uwagi na mądrość typu, która to sugeruje.

Dlaczego makro traci metadane? Jak napisać to makro lub które zawiera metadane?

Odpowiedz

32

^ to makro czytnika. defmacro nigdy go nie zobaczy. Wskazówka znajduje się na liście (unquote name). Porównaj na przykład (meta ^String 'x) z (meta ' ^String x), aby zobaczyć efekt.

Musisz podać wskazówkę na symbol.

(defmacro def-string 
    [name value] 
    `(def ~(vary-meta name assoc :tag `String) ~value)) 

A Zastosowanie:

user=> (def-string foo "bar") 
#'user/foo 
user=> (meta #'foo) 
{:ns #<Namespace user>, :name foo, :file "NO_SOURCE_PATH", :line 5, :tag java.lang.String} 
+1

Ahh! Oczywiście makra czytników są oceniane przed defmacros. Dzięki. – Ralph

5

Metadane nie pojawi się w macroexpand ponieważ miało być "niewidzialne".

Jeśli makro jest poprawne (co nie jest prawdą), powinieneś być w stanie wywołać (meta # 'db-host-opcja), aby sprawdzić metadane na var.

Należy zwrócić uwagę, że (def sym ...) wstawia metadane do var, które otrzymuje z symbolu. Ale nazwa^Tag ~ ustawia metadane na ~ nazwisko (nazwa bez nazwy), a nie na przekazany symbol związany z nazwą. Nie może zrobić nic innego, ponieważ^Tag ... przetwarzanie jest wykonywane przez czytnik, który jest już zakończony po rozpoczęciu ekspansji makr.

Chcesz coś

(defmacro def-string [name value] 
    `(def ~(with-meta name {:tag String}) ~value)) 


user> (def-string bar 1) 
#'user/bar 
user> (meta #'bar) 
{:ns #<Namespace user>, :name bar, :file "NO_SOURCE_FILE", :line 1, :tag java.lang.String}