- 1、原创力文档(book118)网站文档一经付费(服务费),不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。。
- 2、本站所有内容均由合作方或网友上传,本站不对文档的完整性、权威性及其观点立场正确性做任何保证或承诺!文档内容仅供研究参考,付费前请自行鉴别。如您付费,意味着您自己接受本站规则且自行承担风险,本站不退款、不进行额外附加服务;查看《如何避免下载的几个坑》。如果您已付费下载过本站文档,您可以点击 这里二次下载。
- 3、如文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“版权申诉”(推荐),也可以打举报电话:400-050-0827(电话支持时间:9:00-18:30)。
- 4、该文档为VIP文档,如果想要下载,成为VIP会员后,下载免费。
- 5、成为VIP后,下载本文档将扣除1次下载权益。下载后,不支持退款、换文档。如有疑问请联系我们。
- 6、成为VIP后,您将拥有八大权益,权益包括:VIP文档下载权益、阅读免打扰、文档格式转换、高级专利检索、专属身份标志、高级客服、多端互通、版权登记。
- 7、VIP文档为合作方或网友上传,每下载1次, 网站将根据用户上传文档的质量评分、类型等,对文档贡献者给予高额补贴、流量扶持。如果你也想贡献VIP文档。上传文档
查看更多
第 8 章 何时使⽤宏
第 8 章 何时使⽤宏
我们如何知道⼀个给定的函数是否真的应该是函数,⽽不是宏呢?多数时候,会很容
易 清楚在哪种情况下需要⽤到宏,哪种情况不需要。缺省情况下,我们应该⽤函
数,因为如果函数能解决问题,⽽偏要⽤上宏的话,会让程序变得不优雅。我们应当
只有在宏能带来特别的好处时才使⽤它们。
什么情况下,宏能给我们带来优势呢?这就是本章的主题。通常这不是锦上添花,⽽
是⼀种必须。⼤多数我们⽤宏可以做到的事情,函数都⽆法完成。第 8.1 节列出了只
能⽤宏来实现的⼏种操作符。尽管如此,也有⼀⼩类 (但很有意思的) 情况介于两者
之间,对它们来说,不管把操作符实现成函数还是宏似乎都⾔之有理。对于这种情
况,第 8.2 节给出了关于宏的正反两⽅⾯考量。最后,在充 考察了宏的能⼒后,我
们在第 8.3 节⾥转向⼀个相关问题:⼈们都⽤宏⼲什么?
8.1 当别⽆他法时
优秀设计的⼀个通⽤原则就是:当你发现在程序中的⼏处都出现了相似的代码时,就
应该写⼀个⼦例程,并把那些相似的语句换成对这个⼦例程的调⽤。如果也把这条原
则⽤到 Lisp 程序上,就必须先决定这个 ⼦例程 应该是函数还是宏。
有时,可以很容易确定应当写⼀个宏⽽不是函数,因为只有宏才能满⾜需求。⼀个像
1+ 这样的函数或许既可以写成函数也可以写成宏:
(defun 1+ (x) (+ 1 x))
(defmacro 1+ (x) (+ 1 ,x))
但是来⾃第 7 .3 节的 while ,则只能被定义成宏:
(defmacro while (test body body)
(do ()
((not ,test))
,@body))
⽆法⽤函数来重现这个宏的⾏为。while 的定义⾥拼接了⼀个作为 body 传⼊ do 的
主体⾥的表达式,它只有当 test 表达式返回 nil 时才会被求值。没有函数可以做到
这⼀点;是因为在函数调⽤⾥,所有的参数在函数调⽤开始之前就会被求值。
当你需要⽤宏时,你看中了它哪⼀点呢?宏有两点是函数⽆法做到的:宏可以控制
(或阻⽌) 对其参数的求值,并且它可以展开进⼊到主调⽅的上下⽂中。任何需要宏
的应⽤,归根到底都是要⽤上述两个属性中的⾄少⼀个。
宏不对其参数进⾏求值 ,这个⾮正式的说法不太准确。更确切的说法应该是,宏能
控制宏调⽤中参数的求值。取决于参数在宏展开式中的位置,它们可以被求值⼀
次,多次,或者根本不求值。宏的这种控制主要体现在四个⽅⾯:
1. 变换
Common Lisp 的 setf 宏就是这类宏中的⼀员,它们在求值前都会对传⼊的参数严加检
查。内置的访问函数 (access function ) 通常都有⼀个对应的逆操作,其作⽤是对该
访问函数所获取的对象赋值。car 的逆操作是 rplaca ,对于 cdr 来说是 rplacd ,等等。
有了 setf ,我们就可以把对这些访问函数的调⽤当成变量赋值。(setf (car x)
a) 就是个例⼦,这个表达式可以展开成 (progn (rplaca x a) a).
为了有这样的效果,setf 必须⾮常了解它的第⼀个参数。如果要知道上述的情况需要
⽤到 rplaca ,
setf 就得清楚它的第⼀个参数是个以 car 开始的表达式。这样的话,setf 以及其他修
改参数的操作符,就必须被写成宏。
2. 绑定
词法变量必须在源代码中直接出现。例如,由于 setq 的第⼀个参数是不求值的,所
以,所有在setq 之上构建的东西都必须是展开到setq 的宏,⽽不能是调⽤它的函数。
对于 let 这样的操作符也是如此,它的实参必须作为 lambda 表达式的形参出现,还有
类似 do 这样展开到 let 的宏也是这样,等等。任何新操作符,只要它修改了参数的词
法绑定,那么它就必须写成宏。
3. 条件求值
函数的所有参数都会被求值。在像 when 这样的结构⾥,我们希望⼀些参数仅在特定
条件下才被求值。只有通过宏才可能获得这种灵活性。
4. 多重求值
函数的所有参数不但都会被求值,⽽且求值的次数都正好是⼀次。我们需要⽤宏来定
义像 do 这样的结构,这样⼦,就可以对特定的参数多次求值。
也有⼏种⽅式可以利⽤宏产⽣的内联展开式带来的优势。这⾥必须强调⼀点,宏展开
后⽣成的展开式将会出现在宏调⽤所在的词法环境之中,因为下列三种⽤法有两种都
基于这个事实。它们是:
5. 利⽤调⽤⽅环境
宏⽣成的展开式可以含有这样的变量,变量的绑
文档评论(0)