I'm writing a macro that looks through the metadata on a given symbol and removes any entries that are not keywords, i.e. the key name doesn't start with a ":" e.g.
(meta (var X)) ;; Here's the metadata for testing...
=>
{:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init11598934441516564808.clj",
:name X,
:ns #object[clojure.lang.Namespace 0x12ed80f6 "thic.core"],
OneHundred 100,
NinetyNine 99}
I want to remove entryes "OneHundred" and "NinetyNine" and leave the rest of the metadata untouched.
So I have a bit of code that works:
(let [Hold# (meta (var X))] ;;Make a copy of the metadata to search.
(map (fn [[kee valu]] ;;Loop through each metadata key/value.
(if
(not= \: (first (str kee))) ;; If we find a non-keyword key,
(reset-meta! (var X) (dissoc (meta (var X)) kee)) ;; remove it from X's metadata.
)
)
Hold# ;;map through this copy of the metadata.
)
)
It works. The entries for "OneHundred" and "NinetyNine" are gone from X's metadata.
Then I code it up into a macro. God bless REPL's.
(defmacro DelMeta! [S]
`(let [Hold# (meta (var ~S))] ;; Hold onto a copy of S's metadata.
(map ;; Scan through the copy looking for keys that DON'T start with ":"
(fn [[kee valu]]
(if ;; If we find metadata whose keyname does not start with a ":"
(not= \: (first (str kee)))
(reset-meta! (var ~S) (dissoc (meta (var ~S)) kee)) ;; remove it from S's metadata.
)
)
Hold# ;; Loop through the copy of S's metadata so as to not confuse things.
)
)
)
Defining the macro with defmacro works without error.
macroexpand-1 on the macro, e.g.
(macroexpand-1 '(DelMeta! X))
expands into the proper code. Here:
(macroexpand-1 '(DelMeta! X))
=>
(clojure.core/let
[Hold__2135__auto__ (clojure.core/meta (var X))]
(clojure.core/map
(clojure.core/fn
[[thic.core/kee thic.core/valu]]
(if
(clojure.core/not= \: (clojure.core/first (clojure.core/str thic.core/kee)))
(clojure.core/reset-meta! (var X) (clojure.core/dissoc (clojure.core/meta (var X)) thic.core/kee))))
Hold__2135__auto__))
BUT!!!
Actually invoking the macro at the REPL with a real parameter blatzes out the most incomprehensible error message:
(DelMeta! X) ;;Invoke DelMeta! macro with symbol X.
Syntax error macroexpanding clojure.core/fn at (C:\Users\Joe User\AppData\Local\Temp\form-init11598934441516564808.clj:1:1).
([thic.core/kee thic.core/valu]) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
(thic.core/kee thic.core/valu) - failed: Extra input at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list
Oh, all-powerful and wise Clojuregods, I beseech thee upon thy mercy. Whither is my sin?
You don't need a macro here. Also, you are misunderstanding the nature of a Clojure
keyword
, and the complications of a Clojure Var vs a local variable.Keep it simple to start by using a local "variable" in a
let
block instead of a Var:So we see the value of x, x2, and x3 is constant. That is the purpose of metadata. The 2nd set of tests shows the effects on the metadata of using
vary-meta
, which is the best way to change the value.When we use a Var, it is not only a global value, but it is like a double-indirection of pointers in C. Please see this question:
This answer also clarifies the difference between a string, a symbol, and a keyword. This is important.
Consider this code
and the result:
So we have added the key
:my
to the metadata as desired. How can we alter it? For a Var, use the functionalter-meta!
So we have added 2 new entries to the metadata map. One has the keyword
:your
as key with value 25, the other has the symbolabc
as key with value:def
(a keyword).We can also use
alter-meta!
to remote a key/val pair from the metadata map:Keyword vs Symbol vs String
A string literal in a source file has double quotes at each end, but they are not characters in the string. Similarly a keyword literal in a source file needs a leading colon to identify it as such. However, neither the double-quotes of the string nor the colon of the keyword are a part of the
name
of that value.Thus, you can't identify a keyword by the colon. You should use these functions to identify different data types:
the above are from the Clojure CheatSheet. So, the code you really want is:
with a sample:
and result:
The above code was all run using this template project.