如何根据参数生成一系列 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)))
数据 > 函数 > 宏。
我可以用宏“生成”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
然后像(:resource-1 my-processed-resources)
这让您回到只使用函数的状态。
(def my-processed-set
(my-processing-function '(a b c)))
数据 > 函数 > 宏。