我一直在读sicp试图理解方案,特别是宏。我注意到sicp根本没有谈论宏。我在Paul Graham的网站上看到:
Viaweb编辑器的源代码大概是20-25%的宏。宏比普通的Lisp函数更难编写,并且当它们不必要时使用它们被认为是不好的风格。所以代码中的每个宏都在那里,因为它必须是。
所以我非常想知道如何编写宏以及如何使用它们,所以我读了这个关于宏的网站:http://www.willdonnelly.net/blog/scheme-syntax-rules/ 但该网站只是解释了如何编写“for”宏。我认为保罗格雷厄姆谈论CL和其他关于计划的博客谈话,但它们部分相同。 那么,wat可以是普通程序无法做到的事情的一个例子,因此必须用宏来完成?
编辑:我已经看过类似的question了,但是想问一下你是否有某种疯狂的算法可以用宏来做,这些算法无法通过程序完成(除了语法糖之外)在那个问题的答案中描述。)答案 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这样的值语言,普通的程序(函数)必须在主体之前评估它的参数。因此,if
,and
,or
,for
,while
等特殊表单无法通过普通程序实现(至少不使用< em> thunks ,如(lambda () body)
,它们是为延迟body
的评估而引入的,可能会产生性能开销);另一方面,其中许多可以实现宏,就像RnRS中所做的那样(例如,参见and
define-syntax
的定义第69页。)
答案 2 :(得分:1)
简单的答案是,您可以使用新语法来延迟评估对功能至关重要。想象一下,您希望新的if
与if-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)中的新语言功能。