【问题标题】:What can you do with macros that can't be done with procedures? [closed]你能用过程不能完成的宏来做什么? [关闭]
【发布时间】:2016-11-18 01:43:36
【问题描述】:

我一直在阅读 sicp 试图理解方案,特别是宏。我注意到 sicp 根本不谈论宏。我在 Paul Graham 的网站上读到:

Viaweb 编辑器的源代码大概有 20-25% 的宏。宏比普通的 Lisp 函数更难编写,并且在不需要时使用它们被认为是不好的风格。因此,该代码中的每个宏都存在,因为它必须存在。

所以我非常想知道如何编写宏以及如何使用它们,所以我阅读了这个关于宏的网站:http://www.willdonnelly.net/blog/scheme-syntax-rules/ 但是该站点只是解释了如何编写“for”宏。我认为 Paul Graham 谈论 CL,另一个博客谈论方案,但它们部分相同。 那么,wat 可以是普通过程无法完成的事情的示例,因此必须使用宏来完成?

编辑:我已经看到了类似的question,但想问一下是否有某种疯狂的算法可以用宏来做,而宏不能用过程来做(除了语法糖,如这个问题的答案)。

【问题讨论】:

标签: functional-programming scheme lisp


【解决方案1】:

宏是一个棘手的话题,我个人已经颠倒过不少于十几次自己的观点,所以对一切都持保留态度。

如果您刚刚熟悉宏,您会发现最有帮助的是那些阐明或加速现有表达式的宏。

您可能接触过的这样一个宏是 照应 宏,它在今天仍然像 Paul Graham 创造这个术语的那一天一样流行:

(define-syntax (aif x)
  (syntax-case x ()
    [(src-aif test then else)
     (syntax-case (datum->syntax-object (syntax src-aif) '_) ()
       [_ (syntax (let ([_ test]) (if (and _ (not (null? _))) then else)))])]))

这些宏引入了“照应”变量,即it,您可以在 if 语句的后续子句和替代子句中使用它,例如:

(aif (+ 2 7)
  (format nil "~A does not equal NIL." it)
  (format nil "~A does equal NIL." it))

这样可以省去输入 let 语句的麻烦——这可以在大型项目中加起来。

更广泛地说,改变程序结构、动态生成“模板化”代码或以其他方式“破坏”规则(希望您知道足以破坏!)的转换是宏的传统领域。我可以继续写下我是如何使用宏简化无数项目和任务的——但我想你会明白的。

学习 Scheme 风格的宏有点令人生畏,但我向你保证,有 excellent guides to syntax-rules-style macros for the merely eccentric,随着你对宏的经验越来越多,你可能会得出这样的结论:它们是个好主意,名副其实的“方案”。

如果您碰巧使用 Racket(以前称为 PLT 方案)--It has excellent documentation on macros,甚至在此过程中提供一些巧妙的技巧(我也猜想那里写的大部分内容可以很容易地用另一个方案编写方言没有问题)

【讨论】:

  • 让我看看我是否理解。宏可以是一门高级学科,需要大量的方案知识,但它们允许程序员简化方案的语法?因为我认为宏是一些使 lsip 独一无二的黑魔法咒语,而不是使 lisp 更具可读性(在这种情况下,我认为使用 lisp 根本没有多大意义)
  • 不,这不是简化语言,而是扩展它。这是您在大多数其他语言中根本无法做到的。看看这个,例如:synthcode.com/scheme/match.scm 想想其他语言中的关键字。如果某个关键字不存在,则不能添加。在 Scheme 或一般的 Lisp 中,您可以。
  • 我认为扩展语言在这里真正重要。这是大多数其他语言无法做到的。通常,在其他语言中,我们可以更改某些关键字的行为,但不能添加更多。例如,C++ 中的运算符覆盖。
  • 想我明白了。我还有很多阅读要做,但我想我开始明白这里发生了什么。非常感谢!
【解决方案2】:

简单的答案是,您可以在延迟评估对功能至关重要的情况下创建新语法。想象一下,您想要一个与if-elseif 工作方式相同的新if。在 Lisp 中,它类似于cond,但没有明确的begin。示例用法是:

(if* (<= 0 x 9) (list x)
     (< x 100)  (list (quotient x 10) (remainder x 10))
     (error "Needs to be below 100"))

不可能将其作为一个过程来实现。我们可以通过要求用户给我们 thunk 来实现相同的工作,因此这些部分将变成可以运行的程序:

(pif* (lambda () (<= 0 x 9)) (lambda () (list x))
      (lambda () (< x 100)) (lambda () (list (quotient x 10) (remainder x 10)))
      (lambda () (error "Needs to be below 100")))

现在很容易实现:

(define (pif* p c a . rest)
  (if (p)
      (c)
      (if (null? rest)
          (a)
          (apply pif* a rest))))

这就是 JavaScript 和几乎所有其他不支持宏的语言的做法。现在,如果您想赋予用户制作第一个而不是第二个的能力,您需要宏。您的宏可以将第一个写入第二个,以便您的实现可以是一个函数,或者您可以将代码更改为嵌套 if:

(define-macro if*
  (syntax-rules ()
    ((_ x) (error "wrong use of if*"))
    ((_ p c a) (if p c a))
    ((_ p c next ...) (if p c (if* next ...)))))

或者,如果您想使用该过程,只需将每个参数包装在 lambda 中就更简单了:

(define-syntax if*
 (syntax-rules ()
   ((_ arg ...) (pif* (lambda () arg) ...)))

宏的需要是减少样板文件并简化语法。当您看到相同的结构时,您应该尝试将其抽象为一个过程。如果这是不可能的,因为参数以特殊形式使用,您可以使用宏来完成。

Babel,一个 ES6 到 ES5 的转换器,将 JavaSript ES6 语法转换为 ES5 代码。如果 ES5 有宏,那就像制作可兼容宏来支持 ES6 一样容易。事实上,该语言较新版本的大多数特性都是不必要的,因为程序员不必等待一种语言的新版本来赋予它新的花哨特性。如果 Algol 语言(PHP、Java、JavaScript、Python)具有卫生的宏支持,几乎不需要任何新的语言功能。

【讨论】:

    【解决方案3】:

    这是一个标准答案(其中的一部分也包含在 SICP 中 - 例如,请参见 Exercise 1.6;还可以在 text 中搜索关键字“特殊形式”):使用按值调用的语言,例如Scheme,一个普通的过程(函数)必须在主体之前评估它的参数。因此,ifandorforwhile 等特殊形式无法通过普通程序实现(至少不用像@987654329 这样的thunks @,这是为了延迟对 body 的评估而引入的,可能会产生性能开销);另一方面,它们中的许多可以用宏来实现,就像在RnRS 中所做的那样(例如,参见第 69 页上define-syntaxand 的定义)。 p>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-17
      • 2011-01-05
      • 1970-01-01
      • 1970-01-01
      • 2020-01-10
      • 2016-04-24
      相关资源
      最近更新 更多