问题描述
我正在编写一个宏来查看给定符号的元数据并删除所有不是关键字的条目,即键名不以“:”开头,例如
(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}
我想删除条目“OneHundred”和“NinetyNine”,并保持其余元数据不变。
所以我有一些有效的代码:
(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.
)
)
它有效。 “OneHundred”和“NinetyNine”的条目从 X 的元数据中消失了。
然后我把它编码成一个宏。上帝保佑 REPL。
(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.
)
)
)
使用 defmacro 定义宏没有错误。
宏上的 macroexpand-1,例如
(macroexpand-1 '(DelMeta! X))
扩展为正确的代码。这里:
(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__))
但是!!!
实际上在 REPL 中使用实参数调用宏会导致最难以理解的错误消息:
(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
哦,全能和智慧的Clojuregods,我恳求你的怜悯。 我的罪在哪里?
解决方法
这里不需要宏。此外,您误解了 Clojure keyword
的性质,以及 Clojure Var 与局部变量的复杂性。
通过在 let
块中使用本地“变量”而不是 Var 来保持简单:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(dotest
(let [x (with-meta [1 2 3] {:my "meta"})
x2 (vary-meta x assoc :your 25 'abc :def)
x3 (vary-meta x2 dissoc 'abc )]
(is= x [1 2 3])
(is= x2 [1 2 3])
(is= x3 [1 2 3])
(is= (meta x) {:my "meta"})
(is= (meta x2) {:my "meta",:your 25,'abc :def})
(is= (meta x3) {:my "meta",:your 25}))
所以我们看到 x、x2 和 x3 的值是常数。这就是元数据的目的。第二组测试显示了使用 vary-meta
对元数据的影响,这是更改值的最佳方式。
当我们使用一个 Var 时,它不仅是一个全局值,而且在 C 中就像一个指针的双向间接。请看这个问题:
这个答案还阐明了字符串、符号和关键字之间的区别。这很重要。
考虑这个代码
(def ^{:my "meta"} data [1 2 3])
(spyx data)
(spyx-pretty (meta (var data)))
结果:
data => [1 2 3]
(meta (var data)) =>
{:my "meta",:line 19,:column 5,:file "tst/demo/core.cljc",:name data,:ns #object[clojure.lang.Namespace 0x4e4a2bb4 "tst.demo.core"]}
(is= data [1 2 3])
(is= (set (keys (meta (var data))))
#{:my :line :column :file :name :ns})
因此,我们已根据需要将密钥 :my
添加到元数据中。我们怎样才能改变它? 对于 Var,使用函数 alter-meta!
(alter-meta! (var data) assoc :your 25 'abc :def)
(is= (set (keys (meta (var data))))
#{:ns :name :file 'abc :your :column :line :my})
因此我们向元数据映射添加了 2 个新条目。一个使用关键字 :your
作为键,值为 25,另一个使用符号 abc
作为键,值为 :def
(关键字)。
我们还可以使用 alter-meta!
从元数据映射中远程一个键/值对:
(alter-meta! (var data) dissoc 'abc )
(is= (set (keys (meta (var data))))
#{:ns :name :file :your :column :line :my})
关键字 vs 符号 vs 字符串
源文件中的字符串文字 在每一端都有双引号,但它们不是字符串中的字符。类似地,源文件中的关键字文字 需要一个前导冒号来标识它。但是,字符串的双引号和关键字的冒号都不是该值的 name
的一部分。
因此,您无法通过冒号识别关键字。您应该使用这些函数来识别不同的数据类型:
以上来自Clojure CheatSheet。所以,你真正想要的代码是:
(defn remove-metadata-symbol-keys
[var-obj]
(assert (var? var-obj)) ; verify it is a Var
(doseq [k (keys (meta var-obj))]
(when (not (keyword? k))
(alter-meta! var-obj dissoc k))))
带样品:
(def ^{:some "stuff" 'other :things} myVar [1 2 3])
(newline) (spyx-pretty (meta (var myVar)))
(remove-metadata-symbol-keys (var myVar))
(newline) (spyx-pretty (meta (var myVar)))
和结果:
(meta (var myVar)) =>
{:some "stuff",other :things,; *** to be removed ***
:line 42,:name myVar,:ns #object[clojure.lang.Namespace 0x9b9155f "tst.demo.core"]}
(meta (var myVar)) => ; *** after removing non-keyword keys ***
{:some "stuff",:line 42,:ns #object[clojure.lang.Namespace 0x9b9155f "tst.demo.core"]}
以上代码都是使用this template project运行的。