环境:Clojure 1.4
我正试图从函数向量中动态提取函数元数据。
(defn #^{:tau-or-pi: :pi} funca "doc for func a" {:ans 42} [x] (* x x))
(defn #^{:tau-or-pi: :tau} funcb "doc for func b" {:ans 43} [x] (* x x x))
(def funcs [funca funcb])
现在,检索REPL中的元数据(有点)是直截了当的:
user=>(:tau-or-pi (meta #'funca))
:pi
user=>(:ans (meta #'funca))
42
user=>(:tau-or-pi (meta #'funcb))
:tau
user=>(:ans (meta #'funcb))
43
但是,当我尝试使用地图从元数据中获取:ans
,:tau-or-pi
或基本:name
时,我得到了例外情况:
user=>(map #(meta #'%) funcs)
CompilerException java.lang.RuntimeException: Unable to resolve var: p1__1637# in this context, compiling:(NO_SOURCE_PATH:1)
在进行了一些搜索之后,我从2009年的帖子(https://groups.google.com/forum/?fromgroups=#!topic/clojure/VyDM0YAzF4o)中得到了以下想法:
user=>(map #(meta (resolve %)) funcs)
ClassCastException user$funca cannot be cast to clojure.lang.Symbol clojure.core/ns-resolve (core.clj:3883)
我知道defn
宏(在Clojure 1.4中)将Var
的元数据放在def
宏的defn
部分中,这就是为什么简单(meta #'funca)
正在运行,但有没有办法动态获取函数元数据(如上面的map
示例中所示)?
也许我在语法上遗漏了一些东西,但如果有人能指出我正确的方向或正确的方法,那就太棒了。
感谢。
答案 0 :(得分:3)
表达式#(meta #'%)
是一个扩展为对defn
(实际上是def
)的调用的宏,其中有一个名为p1__1637#的参数,该参数是使用gensym
生成的调用meta就是尝试将此本地参数用作var,因为没有var存在该名称会导致此错误。
如果你以var
s的向量而不是函数向量开头,那么你可以将元映射到它们上面。您可以在任何使用函数的地方使用var(非常接近),每次调用var时,查找var的内容的运行时成本非常小。
user> (def vector-of-functions [+ - *])
#'user/vector-of-functions
user> (def vector-of-symbols [#'+ #'- #'*])
#'user/vector-of-symbols
user> (map #(% 1 2) vector-of-functions)
(3 -1 2)
user> (map #(% 1 2) vector-of-symbols)
(3 -1 2)
user> (map #(:name (meta %)) vector-of-symbols)
(+ - *)
user>
所以在您的原始代码中添加几个#'
并删除额外的尾随:应该可以做到这一点:
user> (defn #^{:tau-or-pi :pi} funca "doc for func a" {:ans 42} [x] (* x x))
#'user/funca
user> (defn #^{:tau-or-pi :tau} funcb "doc for func b" {:ans 43} [x] (* x x x))
#'user/funcb
user> (def funcs [#'funca #'funcb])
#'user/funcs
user> (map #(meta %) funcs)
({:arglists ([x]), :ns #<Namespace user>, :name funca, :ans 42, :tau-or-pi :pi, :doc "doc for func a", :line 1, :file "NO_SOURCE_PATH"} {:arglists ([x]), :ns #<Namespace user>, :name funcb, :ans 43, :tau-or-pi :tau, :doc "doc for func b", :line 1, :file "NO_SOURCE_PATH"})
user> (map #(:tau-or-pi (meta %)) funcs)
(:pi :tau)
user>
答案 1 :(得分:2)
最近,我发现将元数据附加到函数本身而不是像defn
那样附加变量是有用的。
你可以用好的def
:
(def funca ^{:tau-or-pi :pi} (fn [x] (* x x)))
(def funcb ^{:tau-or-pi :tau} (fn [x] (* x x x)))
这里,元数据已附加到函数中,然后这些元数据函数被绑定到变量。
关于这一点的好处是,在考虑元数据时,您不再需要担心变量。由于函数包含元数据,因此您可以直接从它们中提取元数据。
(def funcs [funca funcb])
(map (comp :tau-or-pi meta) funcs) ; [:pi :tau]
显然def
的语法与函数的defn
不完全一样精确,因此根据您的用法,您可能有兴趣重新实现defn
以附加元数据功能。
答案 2 :(得分:0)
我想详细说明Beyamor's answer。对于我正在编写的一些代码,我正在使用它:
(def ^{:doc "put the-func docstring here" :arglists '([x])}
the-func
^{:some-key :some-value}
(fn [x] (* x x)))
是的,拥有两个元数据映射有点笨拙。这就是我这样做的原因:
第一个元数据附加到the-func
var。所以你可以使用(doc the-func)
返回:
my-ns.core/the-func
([x])
put the-func docstring here
第二个元数据附加到函数本身。这允许您使用(meta the-func)
返回:
{:some-key :some-value}
总之,当您想要REPL中的文档字符串以及对函数元数据的动态访问时,这种方法会派上用场。