【问题标题】:Is there a Clojure macro equivalent to def?是否有与 def 等效的 Clojure 宏?
【发布时间】:2019-11-18 09:58:37
【问题描述】:

我想编写一个宏 sym-def,它的行为与特殊形式 def 相同,但使用 (symbol "c") 作为第一个参数。

我的第一步是

(def (symbol "c") 4)

但这返回了错误First argument to def must be a Symbol

我的第二步是

(eval `(def ~(symbol "c") 4))

这成功地将c 定义为全局环境中的4。 为什么我的第一步失败了,而第二步成功了?

最后,我尝试编写所需的宏

(defmacro sym-def [sym value] `(def ~sym ~value))

但这有一个“坏”macroexpand

(macroexpand '(sym-def (symbol "c") 4)) => (def (symbol "c") 4)

这样

(sym-def (symbol "c") 4)

失败并出现与我第一步相同的错误。

编写宏的正确方法是什么?

【问题讨论】:

    标签: function clojure macros


    【解决方案1】:

    def 不计算它的第一个参数。想象一下,如果发生了混乱!你不会写

    (def x 1)
    

    因为它会首先尝试评估x,然后因为x 尚未定义而失败!现在,由于它不评估它的论点,显然这是有道理的

    (def (symbol "c") 4)
    

    不起作用,就像

    (def 'c 4)
    

    不会。 def 要求其第一个参数是文字符号。你没有文字符号,所以你不能使用def

    但是有一种较低级别的机制可以与命名空间中的映射进行交互。在这种情况下,您需要clojure.core/intern

    (intern *ns* (symbol "c") 4)
    

    intern 是一个普通函数,因此它会评估其所有参数,这意味着您可以以任何您想要的疯狂方式构造您的 var 名称。然后它会向给定的命名空间添加一个 var,将您的符号映射到所需的值。

    【讨论】:

    • ...所以我们可以将def定义为intern的宏...使用defmacro定义为...哎呀...def
    • 如果 def 要求它的第一个参数是文字符号,为什么 (eval `(def ~(symbol "c") 4)) 有效?
    • @cfgauss 您首先要评估“未引用”的内部部分,所以当它进行评估时,它已经是它需要的形式了。
    • @cfgauss 尝试删除那里的eval:它的计算结果为(def c 4)。然后,当然在结果上运行eval 是非常好的:def 的参数是一个文字符号def 被实际调用时
    • @Thumbnail 即使引导不是问题,你也不能真正做到这一点,因为def 允许像(def factorial (fn [x] (if (zero? x) 1, (* x (factorial (dec x)))))) 这样的循环定义。 intern 不允许这样做。
    【解决方案2】:

    您要编写的宏的正确格式如下:

    (defmacro sym-def [s v] `(def ~(eval s) ~v))
    

    ...或等效:

    (defmacro sym-def [s v] (list `def (eval s) v))
    

    您只需要计算宏内的第一个参数,因为宏的参数在应用时不会被计算。如果您要使用的唯一产生符号的表达式是对symbol 的调用,您可能更喜欢以下宏:

    (defmacro defsym [s v] (list `def (symbol s) v))
    

    ...和它的伴侣:

    (defmacro sym [s] (symbol s))
    

    这些宏将字符串或符号转换为符号。以下是它们的一些使用示例:

    (defsym "the first natural number" 0)
    ;=> #'user/the first natural number
    (sym "the first natural number")
    ;=> 0
    (defsym pi 3.14159) ;same as: (def pi 3.14159)
    ;=> #'user/pi
    (sym pi) ;same as: pi
    ;=> 3.14159
    

    下面给出的变化也可能有用:

    (defmacro defsym* [s v] (list `def (symbol (eval s)) v))
    (defmacro sym* [s] (symbol (eval s)))
    

    他们在评估字符串/符号生成表达式后将其转换为符号。以下是defsym* 的一些使用示例:

    (defsym* "abc" "xyz")
    (defsym* (str \a \b \c) "xyz")
    (defsym* (symbol "abc") "xyz")
    (defsym* 'abc "xyz")
    ;all the previous are equivalent and what follows is valid for any of them
    ;=> #'user/abc
    abc
    ;=> "xyz"
    (defsym* abc 0)
    ;=> #'user/xyz
    abc
    ;=> "xyz"
    xyz
    ;=> 0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-24
      • 2011-07-10
      • 1970-01-01
      • 2016-04-21
      • 2014-12-19
      • 2021-04-30
      • 2010-09-08
      相关资源
      最近更新 更多