【问题标题】:Retrieve Clojure function metadata dynamically动态检索 Clojure 函数元数据
【发布时间】:2012-12-28 18:10:09
【问题描述】:

环境: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 示例)?

也许我在语法上遗漏了一些东西,但如果有人能指出我正确的方向或正确的方法,那就太好了。

谢谢。

【问题讨论】:

    标签: function clojure metadata defn


    【解决方案1】:

    表达式#(meta #'%) 是一个宏,它扩展为对defn(实际上是def)的调用,它有一个名为p1__1637# 的参数,它是由gensym 生成的,并且对meta 的调用试图将此本地参数用作 var,因为不存在具有该名称的 var,您会收到此错误。

    如果您vars 的向量而不是函数向量开始,那么您只需将元映射到它们。您可以在任何使用函数的地方使用 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> 
    

    所以在您的原始代码中添加一对 #'s 并删除额外的尾随 : 应该可以解决问题:

    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> 
    

    【讨论】:

    • 我在构建函数向量时曾考虑过为函数添加前缀,但我想知道是否有办法从函数本身访问元数据。不过,您的答案有效。谢谢,亚瑟。
    【解决方案2】:

    最近,我发现将元数据附加到函数本身而不是像 defn 那样的 vars 很有用。

    你可以通过 def 来做到这一点:

    (def funca ^{:tau-or-pi :pi} (fn [x] (* x x)))
    (def funcb ^{:tau-or-pi :tau} (fn [x] (* x x x)))
    

    在这里,元数据已附加到函数,然后将那些包含元数据的函数绑定到变量。

    这样做的好处是您在考虑元数据时不再需要担心 vars。由于这些函数包含元数据,因此您可以直接从它们中提取它。

    (def funcs [funca funcb])
    
    (map (comp :tau-or-pi meta) funcs) ; [:pi :tau]
    

    显然def 的语法不如defn 的函数那么精细,因此根据您的使用情况,您可能有兴趣重新实现defn 以将元数据附加到函数。

    【讨论】:

    • 我也考虑过在 defn 宏之外执行 def,因为在 1.4 中,defn 宏似乎将 所有 元数据放在var。我倾向于您的方法,因为它没有添加 var 步骤。谢谢。
    【解决方案3】:

    我想详细说明Beyamor's answer。对于我正在编写的一些代码,我正在使用这个:

    (def ^{:doc "put the-func docstring here" :arglists '([x])}
      the-func
      ^{:some-key :some-value}
      (fn [x] (* x x)))
    

    是的,拥有两个元数据映射有点笨拙。这就是我这样做的原因:

    1. 第一个元数据附加到 the-func 变量。所以你可以使用(doc the-func) 返回:

      my-ns.core/the-func
      ([x])
        put the-func docstring here
      
    2. 第二个元数据附加到函数本身。这让你可以使用(meta the-func) 来返回:

      {:some-key :some-value}
      

    总之,当您希望 REPL 中的文档字符串以及对函数元数据的动态访问时,这种方法会派上用场。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多