【问题标题】:Difference between using list and back tick in macros在宏中使用列表和反引号之间的区别
【发布时间】:2012-02-23 17:24:13
【问题描述】:

在概念层面上,LISP(和方言)中的宏获取一段代码(作为列表)并返回另一段代码(再次作为列表)。

根据上述原则,一个简单的宏可以是:

(defmacro zz [a] (list print a))
;macroexpand says : (#<core$print clojure.core$print@749436> "Hello")

但是在clojure中也可以写成:

(defmacro zz [a] `(print ~a))
;macroexpand says : (clojure.core/print "Hello")

我不确定这里的区别以及哪种方式应该是首选方式。第一个看起来很简单,因为我们应该返回列表并避免使用反勾号等奇怪的字符。

【问题讨论】:

    标签: clojure


    【解决方案1】:

    还没有人指出这一点...您的 2 个宏之间的区别在于:您的第二种形式(使用反引号)

    (defmacro zz [a] `(print ~a))
    

    相当于:

    (defmacro zz [a] (list 'print a))
    

    这与您的第一个示例不同:

    (defmacro zz [a] (list print a))
    

    注意缺少的单引号——这就是你的宏扩展不同的原因。我同意其他人的观点:如果您的宏具有相当简单的“形状”,则使用反引号更为传统。如果您必须进行代码遍历或动态构建(即复杂的宏),那么通常可以使用列表并构建它。

    我希望这个解释是有道理的。

    【讨论】:

    • 实际上等价于(list `print a),稍有不同:'print 仅在调用者有print 引用clojure.core/print 而不是本地的或根本没有绑定时才有效(说他们将其从其命名空间的:refer-clojure 中排除)。另一方面,`print 直接扩展为 clojure.core/print,因此它在任何情况下都是明确且正确的。
    • @amalloy 感谢您的回复,根据您的建议,我刚刚尝试在 (print ~a)` and I got back: `(seq (concat (list 'print) (list a)))` - ah, but you're saying the pre-transformed form is more akin to having print 上进行宏扩展(通过粘液),不是吗?
    • 我无法很好地阅读您的回复,因为 SO 正在吃一些反引号字符,但是:Slime 正在向您隐藏命名空间(并且不需要宏扩展)。如果你只是引用表达式,通过在 repl 中输入'`(print ~a),你会看到它等同于(clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/print)) (clojure.core/list a)))clojure.core/print 是我所做的重要区分。
    【解决方案2】:

    在某种程度上,显式构建列表是“最简单的”,因为您需要了解的核心概念很少:只需接受一个列表并对其进行更改,直到获得一个新列表。 Backtick 是“模板化”代码块的便捷快捷方式;没有它也可以编写任何宏,但是对于任何大的宏,它很快就会变得非常不愉快。例如,考虑将let 写成fn 上的宏的两种方式:

    (defmacro let [bindings & body]
      (let [names (take-nth 2 bindings)
            vals (take-nth 2 (rest bindings))]
        `((fn [~@names]
            (do ~@body))
          ~@vals)))
    
    (defmacro let [bindings & body]
      (let [names (take-nth 2 bindings)
            vals (take-nth 2 (rest bindings))]
        (cons (list `fn (vec names) (cons `do body))
              vals)))
    

    在第一种情况下,使用反引号可以清楚地表明您正在编写一个包含主体名称的函数,然后使用值调用它 - 宏代码的“形状”与扩展代码相同,所以你可以想象它会是什么样子。

    在第二种情况下,只有conslist 到处都是,弄清楚扩展的样子真是令人头疼。当然,情况并非总是如此:有时不加反引号会更清楚。

    Kyle Burton 提出了另一个非常重要的观点:print'print 不一样!您的宏扩展应该包含 symbol print,而不是它的值(它是一个函数)。在代码中嵌入对象(例如函数)是非常脆弱的,只能在偶然的情况下起作用。因此,请确保您的宏扩展为您实际上可以自己编写的代码,并让评估系统完成艰苦的工作 - 您可以输入符号 print,但您不能输入指向当前值的指针函数print

    【讨论】:

      【解决方案3】:

      它们之间存在风格差异。您的示例非常简单,但在更复杂的宏中差异会更大。

      例如,《The Joy of Clojure》一书中定义的 unless 宏:

      (defmacro unless [condition & body]
          `(if (not ~condition)
              (do ~@body)))
      

      从书中:

      语法引用允许以下 if-form 充当表达式的一种模板 宏的任何使用都在扩展时变为。

      在创建宏时,请始终选择最易读和惯用的样式。

      相比之下,上面的代码等效地可以写成这样:

      (defmacro unless [condition & body]
        (list 'if (list 'not condition)
                  (list* 'do body)))
      

      【讨论】:

        【解决方案4】:

        根据我的经验,它们是等价的。虽然可能有一些我不知道的极端情况。

        @islon 的例子等价地可以写成:

        相比之下,上面的代码等效地可以写成这样:

        (defmacro unless [condition & body]
          (list 'if (list 'not condition)
                    (list* 'do body)))
        

        【讨论】:

          猜你喜欢
          • 2013-07-21
          • 2011-09-14
          • 1970-01-01
          • 2016-05-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-11
          • 1970-01-01
          相关资源
          最近更新 更多