Lecture Notes: Macros
原论文链接失效,特在这里保存一份
http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html
Lisp functions take Lisp values as input and return Lisp values. They are executed at run-time. Lisp macros take Lisp code as input, and return Lisp code. They are executed at compiler pre-processor time, just like in C. The resultant code gets executed
at run-time. Almost all the errors that result from using macros can be traced to a misunderstanding of this fact.
1. Basic Idea: Macros take unevaluated Lisp code and return a Lisp form. This form should be code that calculates the proper value. Example:
(defmacro Square (X)
'(* ,X ,X))
This means that wherever the pre-processor sees (Square XXX) to replaces it with (* XXX XXX). The resultant code is what the compiler sees.
2. Debugging technique: macroexpand-1
When designing a function, you can type a call to the function into the Lisp Listener (prompt), and see if it returns the correct value. However, when you type a macro "call" into the Lisp Listener, two things happen: first, the macro is expanded into its
resultant code, and then that code is evaluated. It is more useful during debugging to be able to examine the results of these two steps individually. The function
macroexpand-1
returns the result of stage one of this process:
(macroexpand-1 '(Square 9)) ==> (* 9 9)
"If in doubt, macroexpand-1
it out."
3. Purpose: To control evaluation of the arguments.
Since macros are so much harder to use than functions, a good rule of thumb is:
don't use defmacro if defun will work fine
. So, for example, there would be no reason to try to use a macro for
Square
: a function would be much easier to write and test. In Lisp, unlike in C, there is no need to use macros to avoid the very small runtime overhead of a function call: there is a separate method for that (the "inline" proclamation) that lets
you do this without switching to a different syntax. What macros can do that functions cannot is to control when the arguments get evaluated. Functions evaluate all of their arguments before entering the body of the function. Macros don't evaluate any of their
arguments at preprocessor time unless you tell it to, so it can expand into code that might not evaluate all of the arguments. For example, suppose that
cond
was in the language, but if
wasn't, and you wanted to write a version of
if
using cond
.
(defun Iff-Wrong (Test Then &optional Else)
(cond
(Test Then)
(t Else)))
The problem with this is that it always evaluates all of its arguments, while the semantics of
if
dictate that exactly one of the Then
and Else
arguments gets evaluated. For example:
(let ((Test 'A))
(Iff-Wrong (numberp Test)
(sqrt Test)
"Sorry, SQRT only defined for numbers"))
will crash, since it tries to take (sqrt 'A).
A correct version, with behavior identical to the built-in
if
(except that the real if
only has one required arg, not two), would be:
(defmacro Iff (Test Then &optional Else)
"A replacement for IF, takes 2 or 3 arguments. If the first evaluates to
non-NIL, evaluate and return the second. Otherwise evaluate and return
the third (which defaults to NIL)"
'(cond
(,Test ,Then)
(t ,Else)) )
A similar example would be NAND ("Not AND"), which returns true if at least one of the arguments is false, but, like the built-in
and
, does "short-circuit evaluation" whereby once it has the answer it returns immediately without evaluating later arguments.
(defmacro Nand (&rest Args)
'(not (and ,@Args)))
4. Bugs:
- (A) Trying to evaluate arguments at run-time
- (B) Evaluating arguments too many times
- (C) Variable name clashes.
- (A) Trying to evaluate arguments at run-time
Macros are expanded at compiler pre-processor time. Thus, the values of the arguments are generally not available, and code that tries to make use of them will not work. I.e. consider the following definition of
Square
, which tries to replace (Square 4)
with 16
instead of with
(* 4 4)
.
(defmacro Square (X)
(* X X))
This would indeed work for (Square 4)
, but would crash for (Square X)
, since
X
is probably a variable whose value is not known until run-time. Since macros do sometimes make use of variables and functions at expansion time, and to simplify debugging in general,
it is strongly recommended that all macro definitions and any variables and functions that they use at expansion time (as opposed to code they actually expand into) be placed in a separate file that is loaded before any files containing code that makes
use of the macros.
- (B) Evaluating arguments too many times
Let's take another look at our first definition of the Square macro.
(defmacro Square (X) '(* ,X ,X))
This looks OK on first blush. However, try macroexpand-1
'ing a form, and you notice that it evaluates its arguments twice:
(macroexpand-1 '(Square (Foo 2))) ==> (* (Foo 2) (Foo 2))
Foo
gets called twice, but it should only be called once. Not only is this inefficient, but could return the wrong value if
Foo
does not always return the same value. I.e. consider Next-Integer
, which returns 1 the first time called, then 2, then 3.
(Square (Next-Integer))
would return N*(N+1), not N2, plus would advance N by 2. Similarly,
(Square (random 10))
would not necessarily generate a perfect square! With Lisp you have the full power of the language available at preprocessor time (unlike in C), so you can use ordinary Lisp constructs to solve this problem. In this case,
let
can be used to store the result in a local variable to prevent multiple evaluation. There is no general solution to this type of problem in C.
(defmacro Square2 (X)
'(let ((Temp ,X))
(* Temp Temp))) (macroexpand-1 '(Square2 (Foo 2)))
==> (let ((Temp (Foo 2)))
(* Temp Temp))
This is what we want.
- (C) Variable name clashes.
When using let
to suppress multiple evaluation, one needs to be sure that there is no conflict between the local variable chosen and any existing variable names. The above version of
Square2
is perfectly safe, but consider instead the following macro, which takes two numbers and squares the sum of them:
(defmacro Square-Sum (X Y)
'(let* ((First ,X)
(Second ,Y)
(Sum (+ First Second)))
(* Sum Sum)) )
This looks pretty good, even after macroexpansion:
(macroexpand-1 '(Square-Sum 3 4))
==> (LET* ((FIRST 3)
(SECOND 4)
(SUM (+ FIRST SECOND)))
(* SUM SUM))
which gives the proper result. However, this version has a subtle problem. The local variables we chose would conflict with existing local variable names if a variable named
First
already existed. E.g.
(macroexpand-1 '(Square-Sum 1 First))
==> (LET* ((FIRST 1)
(SECOND FIRST)
(SUM (+ FIRST SECOND)))
(* SUM SUM))
The problem here is that (SECOND FIRST)
gets the value of the new
local variable FIRST
, not the one you passed in. Thus
(let ((First 9)) (Square-Sum 1 First))
returns 4, not 100! Solutions to this type of problem are quite complicated, and involve using
gensym
to generate a local variable name that is guaranteed to be unique.
Moral: even seemingly simple macros are hard to get right, so don't use macros unless they really add something. Both
Square
and Square-Sum
are inappropriate uses of macros.
(defmacro Square-Sum2 (X Y)
(let ((First (gensym "FIRST-"))
(Second (gensym "SECOND-"))
(Sum (gensym "SUM-")))
'(let* ((,First ,X)
(,Second ,Y)
(,Sum (+ ,First ,Second)))
(* ,Sum ,Sum))
))
Now
(macroexpand-1 '(Square-Sum2 1 First))
==> (LET* ((#:FIRST-590 1)
(#:SECOND-591 FIRST)
(#:SUM-592 (+ #:FIRST-590 #:SECOND-591)))
(* #:SUM-592 #:SUM-592))
This expansion has no dependence on any local variable names in the macro definition itself, and since the generated ones are guaranteed to be unique, is safe from name collisions.
Lecture Notes: Macros的更多相关文章
- Lecture notes of Mathematical analysis
Lecture notes of Mathematical analysis Preliminary theory Teaching purpose: Mathematical analysis is ...
- Coursera 机器学习Course Wiki Lecture Notes
https://share.coursera.org/wiki/index.php/ML:Main 包含了每周的Lecture Notes,以便复习回顾的时候使用.
- C++基本要点复习--------coursera程序设计实习(PKU)的lecture notes
因为一些特性复杂,很多时候也用不到一些特性,所以忘记了,算是随笔,也当作一个临时查找的手册.没有什么顺序,很杂. 1.构造函数通过函数重载的机制可以有多个(不同的构造函数,参数个数,或者参数类型不同. ...
- 高级算法设计讲义 Lecture Notes for Advanced Algorithm Design
(Last modification: 2012-12-17) Textbooks: (1) David Williamson, David Shmoys. The Design of Approxi ...
- Study notes for B-tree and R-tree
B-tree B-tree is a tree data structure that keeps data sorted and allows searches, sequential access ...
- CS229 Lecture 01
CS229 Lecture notes 01 机器学习课程主要分为4部分:监督学习:学习理论:无监督学习:增强学习. $x^{(i)}$表示特征,$y^{(i)}$表示目标,$i=1...m$.m是训 ...
- (转)Awesome Courses
Awesome Courses Introduction There is a lot of hidden treasure lying within university pages scatte ...
- K-Means 聚类算法
K-Means 概念定义: K-Means 是一种基于距离的排他的聚类划分方法. 上面的 K-Means 描述中包含了几个概念: 聚类(Clustering):K-Means 是一种聚类分析(Clus ...
- Deep learning:五十一(CNN的反向求导及练习)
前言: CNN作为DL中最成功的模型之一,有必要对其更进一步研究它.虽然在前面的博文Stacked CNN简单介绍中有大概介绍过CNN的使用,不过那是有个前提的:CNN中的参数必须已提前学习好.而本文 ...
随机推荐
- xshell无法连接centos 6.6的问题
最近在做MySQL集群配置的预研工作,需要在VirtualBox上安装CentOS 6.6.参照网上的教程安装好centos后,出现了一个问题:虚拟机外部的文本内容无法复制粘贴到CentOS的终端里去 ...
- [翻译]投影变换 Projection Transform (Direct3D 9)
你可以认为投影变换就是控制摄像机内部的一种方式.他可以类推为为摄像机选择一个漏字板.它是三种变换中最难懂的.本文只讨论以下的一些内容. 典型的投影变换就是缩放和透视投影.投影就变换把视椎转化为一个立方 ...
- 图片轮播的JS写法,通用涉及多个轮播
本代码是借鉴大神的代码分析理解后,自己改写的!有不足指出希望给为大神指点. 核心只有一个JS,里面包含了css样式. 展示效果图:
- printf()函数
printf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息. printf()函数的调用格式为: printf("<格式化字符串>", <参 ...
- SELECT时为何要加WITH(NOLOCK)
此文章非原创,仅为分享.学习!! 要提升SQL的查询效能,一般来说大家会以建立索引(index)为第一考虑.其实除了index的建立之外,当我们在下SQL Command时,在语法中加一段WITH ( ...
- xml读写文件实例
在某个通讯中需要向服务器发送请求xml,格式例子如下: <?xml version="1.0" encoding="UTF-8"?> <ROO ...
- 线程入门之实现Runnable接口和继承Thread类
线程的2种使用方式:实现Runnable接口和继承Thread类 1.实现Runnable接口 实现Runnable接口,必须实现run方法,也是Runnable接口中的唯一一个方法 class Ru ...
- CSS的基本操作
<html> <!-- . 给整个页面填一个一个背景 . 给em添加一个样式样倾斜效果消失 . 改变第一层UL的样式为蓝色,16px . 改变第二层的UL的样式为红色 14px . ...
- iOS - ImageCache 网络图片缓存
1.ImageCache 使用内存缓存方式: 使用沙盒缓存方式: 使用网络图片第三方库方式: SDWebImage: iOS 中著名的网络图片处理框架 包含的功能:图片下载.图片缓存.下载进度监听.g ...
- SQL触发器、事务
触发器: 触发器为特殊类型的存储过程,可在执行语言事件时自动生效.SQL Server 包括三种常规类型的触发器:DML 触发器.DDL 触发器和登录触发器. 当服务器或数据库中发生数据定义语言 (D ...