对于无法通过程序执行的宏,您可以做些什么?

时间:2016-07-15 00:17:02

标签: functional-programming scheme lisp

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

  

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

所以我非常想知道如何编写宏以及如何使用它们,所以我读了这个关于宏的网站:http://www.willdonnelly.net/blog/scheme-syntax-rules/ 但该网站只是解释了如何编写“for”宏。我认为保罗格雷厄姆谈论CL和其他关于计划的博客谈话,但它们部分相同。 那么,wat可以是普通程序无法做到的事情的一个例子,因此必须用宏来完成?

编辑:我已经看过类似的question了,但是想问一下你是否有某种疯狂的算法可以用宏来做,这些算法无法通过程序完成(除了语法糖之外)在那个问题的答案中描述。)

3 个答案:

答案 0 :(得分:4)

宏是一个棘手的主题,我个人在不少于十几次的时间内完全颠倒了我自己的观点,所以把所有事情都拿走了。

如果您只是熟悉宏,那么您将发现最有帮助的是那些澄清或加速现有表达式的宏。

你可能接触过的一个宏是照应宏,它今天仍然像保罗格雷厄姆创造这个词一样受欢迎:

(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)))])]))

这些宏介绍" anaphora"变量,即it,可以在if语句的结果和替代子句中使用,例如:

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

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

更广泛地说,改变程序结构的转换,动态生成"模板化"代码或其他" break"规则(你希望知道它足以打破!)是宏的传统领域。我可以写一下如何用宏来简化无数项目和作业 - 但我认为你会得到这个想法。

学习Scheme风格的宏有点令人生畏,但我向你保证有excellent guides to syntax-rules-style macros for the merely eccentric,随着你获得越来越多的宏经验,你可能会得出结论,他们是一个美丽的想法,值得这个名字" Scheme"。

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

简单的答案是,您可以使用新语法来延迟评估对功能至关重要。想象一下,您希望新的ifif-elseif的工作方式相同。在Lisp中,它会像cond一样,但没有明确的begin。示例用法是:

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

将此作为一个程序来实现是不可能的。我们可以通过要求用户给我们thunks来实现相同的功能,因此部件将成为可以运行的程序:

(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和几乎所有其他不支持宏的语言都是这样做的。现在,如果您想让用户有能力制作第一个而不是第二个,那么您需要宏。您的宏可以将第一个写入第二个,以便您的实现可以是一个函数,或者只需将代码更改为嵌套:

(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)中的新语言功能。