【问题标题】:What's the purpose of macros?宏的目的是什么?
【发布时间】:2014-12-30 17:35:54
【问题描述】:

这里我主要关注的是 Scala 和 Lisp/Scheme 宏,而不是 C/C++/Obj-C 中的宏

我只是不明白这一点。

我的理解是,宏是用来扩展语言的。但函数也是如此。

我了解由于某些语言限制,有些事情无法干净地实现,因此需要采用宏。但是我看到的很多关于宏的例子似乎是使用普通函数实现起来相当简单的事情。

那么具体的目的是什么?清理宏或其他方式,请有人赐教。如果可能,请提供一些可以在宏中完成的示例代码,但用普通函数是不可能/很难做到的。

【问题讨论】:

标签: scala macros lisp


【解决方案1】:

对 Scala 宏所做的事情有一个简明的总结:http://scalamacros.org/paperstalks/2014-02-04-WhatAreMacrosGoodFor.pdf。总而言之,众所周知,宏有利于:1) 代码生成,2) 高级静态检查,3) 支持特定领域的语言。让 Scala 中的宏基于类型为类 Lisp 语言中的宏通常具有的功能提供了额外且强大的转折。

上面链接的幻灯片中的一些示例确实可以在没有宏的情况下实现,但结果要么在某种意义上缺乏(例如在性能方面),要么对用户来说过于复杂(例如因为重量级错误消息)。例如,可以想象 Akka 的类型化通道可以使用纯隐式实现,但编译速度和可理解性会受到影响。或者,scala/async 可以实现为编译器插件,但它必须依赖于内部编译器 API,并且更难分发。

当然,宏不是灵丹妙药。显然有一些用例不是最佳选择,这在http://scalamacros.org/paperstalks/2014-03-01-MacrosVsTypes.pdf 中有概述。然而,令人好奇的是,在许多情况下,既不是纯宏解决方案,也不是无宏解决方案,而是精心构建的混合解决方案最终是最好的。

【讨论】:

  • 第一个演示中有几个例子。我不认为没有宏可以实现 1、2、5、7 和 8。 3 和 4 可以通过高级类型级编程来完成,但结果在编译时和运行时都会变慢,并且更难维护。 6 可以近似,但用户体验会受到影响(错误消息、词汇不一致)。
【解决方案2】:

在 Common Lisp 和 Scheme 中,大多数特殊语法确实是根据其他特殊语法实现的,因此是宏。

例如,Scheme 和 CL 都有 ifcondcase,但只有 if 是原始语法。

标准定义的宏和您自己制作的宏并没有什么特别之处。可以使它们的行为和工作与原语一样好。

宏的代价是让读者感到困惑和惊讶。使用像 with-* 这样的通用命名约定可能会有所帮助,但如果函数/过程可以完成这项工作,或者如果表单仅在少数地方使用,则不应使用宏。

【讨论】:

    【解决方案3】:

    在 Lisp 中使用宏有三个主要目的(不了解 Scala):

    • 定义:在适当的地方创建并直接注册某些东西。示例:defundefgenericdefclass(来自标准)、deftable(来自后现代)。
    • Unwind-protect wrappers:状态被临时修改,并确保在完成任务后修改回来。这样重复写可能很麻烦,所以我们创建了一个速记。示例:with-open-file(标准)、with-transaction(许多数据库)。
    • 其他语言的生成:例如,CL-WHO (HTML)、Parenscript (JavaScript)。通过以 Lisp 形式生成其他语言的代码,我们可以为这些其他语言使用宏,即使它们本身不支持。

    具体示例:Java 7 引入了一种简写,用于确保 Closables 在 try-blocks 中的关闭:

    try (SomeClosable foo = openFoo()) {
        foo.doSomething();
    }
    

    在 Java 6 中只能大致这样表达:

    SomeClosable foo;
    try {
        foo = openFoo();
        foo.doSomething();
    } finally {
        if (foo != null && foo.isOpen()) {
            foo.close();
        }
    }
    

    Java 开发人员必须等待语言设计人员实现此功能。 Lisp 开发人员使用了一个小宏:

    (defmacro with-open-foo ((var &rest options) &body body)
      `(let ((,var (open-foo ,@options)))
         (unwind-protect
             (progn ,@body)
           (when ,var (close ,var)))))
    

    这样他就可以写了

    (with-open-foo (f :bar baz)
      (do-some-foo f)
      (and-something-else))
    

    而不是

    (let ((f (open-foo :bar baz)))
      (unwind-protect
          (progn
            (do-some-foo f)
            (and-something-else))
        (when f (close f))))
    

    【讨论】:

    • 你不能用defun把with-open-foo写成一个函数吗?
    • @ElectricCoffee:简单的回答:不。更长的答案:你可以使用回调来做一些事情,但是写起来更复杂,使用起来更麻烦,并且涉及两个额外的函数调用。
    • 很抱歉恢复一个死话题,但我仍然没有看到 您的示例如何从代码生成中受益,并且不能仅使用函数调用来实现。我的意思是为什么在这种情况下,带有变量 args 的函数不起作用?你能说明一下使用起来会很麻烦吗?
    猜你喜欢
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    • 1970-01-01
    • 2012-05-01
    • 2010-09-06
    • 1970-01-01
    相关资源
    最近更新 更多