【问题标题】:How do I generate a sequence of defs based on a parameter如何根据参数生成一系列defs
【发布时间】:2021-07-13 22:08:14
【问题描述】:

我可以用宏“生成”一个 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 的问题“[...] 你为什么要这样做?”:我有一个“资源”的规范(深度嵌套的映射),拥有一个宏会相当方便为这些资源生成 defs(记录),以便以后使用它们。所以我想没有什么不寻常的“它会干掉事情”的原因。 :) 此时我找到了一种方法,将my-names 包装在eval 中。剩下的问题是:这是惯用的,还是有更好的方法?

【问题讨论】:

  • 你能解释一下你为什么要这样做吗?它确实有助于找到合适的答案。如果你想坚持只写一个宏来创建一堆def 语句,请看这个答案:stackoverflow.com/questions/60212576/…
  • 感谢您的回答。我添加了一段来解决您的有效问题。我会研究你链接的答案。
  • @branch14 回答你为什么要这样做:你说你有一个深度嵌套的资源地图。例如,我将使用 postwalk 遍历此地图并构建一个从关键字到资源的大地图(而不是为每个资源生成一个 def):(def resources (postwalk ...))。然后,我只需通过此映射中的键访问资源:(:my-resource resources)(而不是拥有deffed 资源my-resource)。
  • @Rulle 谢谢你的建议。这完全是我在其他情况下会选择(并且已经这样做)的路线。但在这种情况下,从 API 的角度来看,它并不能解决问题。
  • @branch14 我明白了。如果您包含深度嵌套的资源映射的一部分以及应该如何从该映射生成定义的一些示例,那么您很可能会得到一个更好地解决您的问题的答案。

标签: clojure


【解决方案1】:

通常您不能使用宏来根据运行时值生成代码, 您的任务仍然不需要 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

【讨论】:

  • 感谢您鼓舞人心的回答。我不明白“通常你不能使用宏来生成基于运行时值的代码”是真的。但除此之外,我真的很喜欢你实习 vars 的方式。我完全可以看到它有什么用处!
  • 我最终使用了这种方法,因为带有 eval 的宏会导致更多问题。所以我放弃了宏并使用动态实习变量。再次感谢。将此标记为正确答案,因为它很好地解决了我的问题。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    我会创建一个包含所有内容的 var。并允许宏调用者指定他们想要的 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)))
    

    数据 > 函数 > 宏。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-31
      • 1970-01-01
      • 1970-01-01
      • 2013-05-22
      相关资源
      最近更新 更多