Clojure函数自引用以获取自己的元数据

时间:2019-03-30 14:31:34

标签: clojure

当我将一些元数据附加到一个函数然后调用它时,我无法访问该函数中的那些元数据

(let [I (fn I [x] (println I) (println (meta I)))]
  (let [f (with-meta I {:rr 5})]
    (println I)
    (println f)
    (f I)))

我看到来自函数内部的自引用不是实际调用的函数实例,因此通过该自引用没有可用的元数据。我需要自我引用来给我实际调用的函数实例,以访问这些元数据

3 个答案:

答案 0 :(得分:0)

我认为问题在于您将函数的值和函数的标识混合在一起。这是许多其他语言所做的事情,因此在学习Clojure时很自然。在您的示例中,I有一个自身引用,并从该引用中查找元数据,该元数据返回nil。然后,您创建与f相同的I,但具有一些元数据。因此,当您运行f时,它将在I上查找元数据并返回nil。定义f根本不会改变I,它只是根据旧事物创建了新事物。如果要更改某些内容,则需要引入可以更改的引用类型。其中有几种,但是通常要使用Var (see here for reference)

来存储函数
(defn i [] (meta i))
(i)     ;;=> nil
(alter-var-root #'i with-meta {:rr 5})
(i)     ;;=> {:rr 5}

在这里,我们在当前名称空间中定义一个名为i的函数,该函数仅返回其自己的元数据。我们称它为nil。然后,我们使用一些新的元数据更改全局引用,然后再次调用它。

如果您想要一个更具词法范围的示例,则可以使用atom如下:

(let [i (atom nil)
      f (fn [] (meta @i))]
  (reset! i f)
  (prn 'before '>> (@i))
  (swap! i with-meta {:rr 5})
  (prn 'after '>> (@i)))

但是,除了学习如何将这些东西组合在一起之外,我不确定目标是什么。在计划维护的真实程序中尝试使用这些结构可能是一个坏主意。

答案 1 :(得分:0)

我偶然发现了一个使函数能够读取其自身元数据的技巧。看起来,当原始函数定义具有自定义元数据时,Clojure编译器生成元数据支持代码的方式有所不同。如果存在(meta fn-name),则该函数在函数的主体内起作用,否则不起作用。例如,以下将产生OP所需的结果:

*clojure-version*
;;=> {:major 1, :minor 10, :incremental 0, :qualifier nil}

(let [f1 ^{:foo true} (fn f [] (meta f))
      f2 (with-meta f1 {:bar true})]
  (prn (f1))
  (prn (f2)))
;;=> {:foo true}
;;=> {:bar true}
;;=> nil

我们可以检查为函数生成的代码,而没有原始定义中的元数据-只有invoke方法

(require '[clojure.pprint :as p])

(let [ff (fn f [] (meta f))]
  (p/pprint (seq (.getDeclaredMethods (class ff)))))
;;=> (#object[java.lang.reflect.Method 0x2b56b137 "public java.lang.Object user$eval2171$f__2172.invoke()"])
;;=> nil

当存在元数据时,会生成其他方法(metawithMeta)来处理元数据。

(let [ff ^:foo (fn f [] (meta f))]
  (p/pprint (seq (.getDeclaredMethods (class ff)))))
;;=> (#object[java.lang.reflect.Method 0x3983bd83 "public clojure.lang.IObj user$eval2175$f__2176.withMeta(clojure.lang.IPersistentMap)"]
;;=> #object[java.lang.reflect.Method 0x547d182d "public clojure.lang.IPersistentMap user$eval2175$f__2176.meta()"]
;;=> #object[java.lang.reflect.Method 0x62c3d0fe "public java.lang.Object user$eval2175$f__2176.invoke()"])
;;=> nil

答案 2 :(得分:0)

欢迎来到Clojure,@ xstreamer!

我将提出与您要求的(准确地)有所不同的建议。我不知道如何从函数 中查询函数的元数据。因此,我建议您先定义函数,然后再重新定义函数元数据。在Clojure中,这相当简单。

(defn f
  "Boring doc"
  [])

(meta #'f)
;; => {:arglists ([]),
;;     :doc      "Boring doc",
;;     :line     32,
;;     :column   1,
;;     :file     "C:/Users/teodorlu/IdeaProjects/th-scratch/src/th/play/core.clj",
;;     :name     f,
;;     :ns       #object[clojure.lang.Namespace 0x3b402f0c "th.play.core"]}

现在,重新定义它!

(alter-meta! #'f assoc :rr 5)

(meta #'f)
;; => {:arglists ([]),
;;     :doc      "Boring doc",
;;     :line     32,
;;     :column   1,
;;     :file     "C:/Users/teodorlu/IdeaProjects/th-scratch/src/th/play/core.clj",
;;     :name     f,
;;     :ns       #object[clojure.lang.Namespace 0x3b402f0c "th.play.core"],
;;     :rr       5}

assoc在地图上设置值的地方。

(assoc {} :rr 5)
;; {:rr 5}

(assoc {:some :stuff} :more :stuff)
;; {:some :stuff, :more :stuff}

参考文献

如果您对#'f感到困惑,这就是获取表示f 绑定的 var的方法,而不仅仅是它所引用的值。有关var及其使用方法的详细信息,请参考official reference on varsless terse guide from 8th light