如何根据参数生成一系列 def

How do I generate a sequence of defs based on a parameter

我可以用宏“生成”def。

(defmacro my-def [my-name]
  `(def ~my-name 42))

(my-def a)

a; => 42

如果我尝试对列表做类似的事情

(defmacro my-defs [my-names]
  `(do
    ~@(for [name# my-names]
      `(def ~name# 42))))

(my-defs (a b c))

(macroexpand '(my-defs (a b c))); => (do (def a 42) (def b 42) (def c 42))

只要我使用文字列表作为输入,它就可以工作。但是一旦我想传入一个 var

(def my-list '(a b c))

(macroexpand '(my-defs my-list)); => Don't know how to create ISeq from: clojure.lang.Symbol

我很难访问 my-names 的值。我不能使用 ~my-names,因为它已经在非引号拼接 (~@) 中使用,并且会导致“尝试 [...] 调用未绑定的 fn”。

我错过了什么?

我需要使用 (var-get (resolve my-names)) 吗?

在这些情况下,宏是否需要“检测”传递的参数是文字值还是 var 并相应地采取行动以便两者都起作用?

或者使用 eval 来避免这种情况是惯用的吗?

解决@Alan Thompson 的问题“[...] 为什么 [do] 你想这样做?”:我有一个“资源”的规范(一个深度嵌套的地图),拥有它会很方便一个宏为这些资源生成 defs(记录),以便在以后使用它们。所以我想没有什么不寻常的理由“它会把东西弄干”。 :) 这时我找到了一种方法,将 my-names 包装在 eval 中。剩下的问题是:这是惯用的,还是有更好的方法?

宏玩符号。当您使用“我的名字”调用您的宏时,该符号直接进入宏,不会像在函数调用中那样查找 var。然后宏说 (for... 而不是序列,而是一个符号!

至于你应该做什么...好吧,你可以在宏中使用 resolve,但是宏只有在给定一个符号时才会起作用。

通常你不能使用宏来根据运行时值生成代码, 你的任务仍然不需要 clojure 中的宏,因为你可以在命名空间中动态地实习变量:

(defn intern-vals [data]
  (doseq [[var-name var-val] data]
    (intern *ns* var-name var-val)))

user> (intern-vals {'some-val 10 'other-val 20})
;;=> nil

user> some-val
;;=> 10

user> other-val
;;=> 20

请注意,由于 *ns* 动态变量:

,此函数会在调用它的命名空间中保留值
user> (ns a2)

a2> (user/intern-vals {'some-val "asd" 'other-val "xxx"})
;;=> nil

a2> some-val
;;=> "asd"

a2> user/some-val
;;=> 10

Addressing @Alan Thompson's question "[...] why [do] you want to do this?": I have a specification (a deeply nested map) of "resources" and it would be rather handy to have a macro generate defs (records) for these resources in order to use them down the line. So I guess no reason out of the ordinary "It would DRY up things". :) At this time I found a way by wrapping my-names in an eval. The question that remains is: Is this idiomatic, or is there a better way?

我会制作一个包含所有内容的单一变量。并允许宏调用者指定他们想要的 var 名称。从而使宏“卫生”。

他们成为根变量有什么特别的原因吗?

它不起作用的原因是宏传递的是符号 my-list,而不是它的值。所以是的,你可以 eval 它来找到它的价值。

考虑到您首先要执行 (def my-list ...),为什么不将其设为声明已处理数据结构的 def?例如:

(def my-processed-set 
  (my-processing-macro '(a b c)))

或合并

(defresources my-processed-resources '(a b c))

其中 defresources 是您的宏,它将结果集绑定到在 my-processed-resources

中传递的符号引用的 var

然后像(:resource-1 my-processed-resources)

一样使用它们

这让您回到只使用函数的状态。

(def my-processed-set 
  (my-processing-function '(a b c)))

数据 > 函数 > 宏。