在 Clojure 中使用元数据创建相互递归的本地函数



假设我想在本地范围内定义两个相互递归的函数。我可以用letfn做到这一点:

(letfn 
  [(f [x] (if (= x 0) (g x) true))
   (g [x] (if (= x 1) (f x) false))]
 (f 0))

但是与let相比,letfn是相当有限的,因为它只接受"函数规范",而不是任意表达式。我的问题是:如果我想将元数据(使用 with-meta(附加到fg,以便在f内,我可以读取g的元数据,而在g中,我可以读取f的元数据怎么办?这在克洛朱尔可能吗?

(对于上下文,我正在尝试实现一个类似fn的宏,该宏会自动将某些元数据附加到正在创建的函数。我希望这些自动注释的fn可以在普通 Clojure 函数所在的任何地方实例化,包括在letfn内。但是我不明白如何定义一个类似letfn的宏来附加元数据,因为它最终必须脱糖到letfn,它不能附加元数据。

不要忘记with-local-vars

  (with-local-vars [f (fn [x] (if (= x 0) (g x) true))
                    g (fn [x] (if (= x 1) (f x) false))]
    (reset-meta! f {:f 3})
    (reset-meta! g {:g 2})

结果:

(f 0) => false
(f 1) => true
f           => #<Var: --unnamed-->
(var-get f) => #object[tst.demo.core$fn__20698$fn__20699 0x1eb2d718 "tst.demo.core$fn__20698$fn__20699@1eb2d718"]
(meta f)    => {:f 3}
(meta g)    => {:g 2}

您还可以使用 var-getvar-set 来访问/更改本地变量的值。

我找到了以下解决方案,其中letfn用于定义计算实际函数fg的thunks:

(letfn [(f-thunk [] 
          (with-meta (fn f [] (let [g (g-thunk)] (meta g))) {:f 3})) 
        (g-thunk [] 
          (with-meta (fn g [] (let [f (f-thunk)] (meta f))) {:g 2}))]
  (let [f (f-thunk) g (g-thunk)]
    [(f) (g)]))