分享
 
 
 

Teach Yourself Scheme in Fixnum Days 第八章 Lisp 风格的宏

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

[Go to first, previous, next page; contents; index]

Chapter 8

Macros

Users can create their own special forms by defining macros. A macro is a symbol that has a transformer procedure associated with it. When Scheme encounters a macro-expression -- ie, a form whose head is a macro -- , it applies the macro's transformer to the subforms in the macro-expression, and evaluates the result of the transformation.

Ideally, a macro specifies a purely textual transformation from code text to other code text. This kind of transformation is useful for abbreviating an involved and perhaps frequently occurring textual pattern.

A macro is defined using the special form define-macro (but see sec A.3).3 For example, if your Scheme lacks the conditional special form when, you could define when as the following macro:

(define-macro when

(lambda (test .

branch)

(list 'if test

(cons 'begin

branch))))

This defines a when-transformer that would convert a when-expression into the equivalent if-expression. With this macro definition in place, the when-expression

(when (< (pressure tube) 60)

(open-valve tube)

(attach floor-pump tube)

(depress floor-pump 5)

(detach floor-pump tube)

(close-valve tube))

will be converted to another expression, the result of applying the when-transformer to the when-expression's subforms:

(apply

(lambda (test .

branch)

(list 'if test

(cons 'begin

branch)))

'((< (pressure tube) 60)

(open-valve tube)

(attach floor-pump tube)

(depress floor-pump 5)

(detach floor-pump tube)

(close-valve tube)))

The transformation yields the list

(if (< (pressure tube) 60)

(begin

(open-valve tube)

(attach floor-pump tube)

(depress floor-pump 5)

(detach floor-pump tube)

(close-valve tube)))

Scheme will then evaluate this expression, as it would any other.

As an additional example, here is the macro-definition for when's counterpart unless:

(define-macro unless

(lambda (test .

branch)

(list 'if

(list 'not test)

(cons 'begin

branch))))

Alternatively, we could invoke when inside unless's definition:

(define-macro unless

(lambda (test .

branch)

(cons 'when

(cons (list 'not test)

branch))))

Macro expansions can refer to other macros.

8.1 Specifying the expansion as a template

A macro transformer takes some s-expressions and produces an s-expression that will be used as a form. Typically this output is a list. In our when example, the output list is created using

(list 'if test

(cons 'begin

branch))

where test is bound to the macro's first subform, ie,

(< (pressure tube) 60)

and

branch to the rest of the macro's subforms, ie,

((open-valve tube)

(attach floor-pump tube)

(depress floor-pump 5)

(detach floor-pump tube)

(close-valve tube))

Output lists can be quite complicated. It is easy to see that a more ambitious macro than when could lead to quite an elaborate construction process for the output list. In such cases, it is more convenient to specify the macro's output form as a template, with the macro arguments inserted at appropriate places to fill out the template for each particular use of the macro. Scheme provides the backquote syntax to specify such templates. Thus the expression

(list 'IF test

(cons 'BEGIN

branch))

is more conveniently written as

`(IF ,test

(BEGIN ,@

branch))

We can refashion the when macro-definition as:

(define-macro when

(lambda (test .

branch)

`(IF ,test

(BEGIN ,@

branch))))

Note that the template format, unlike the earlier list construction, gives immediate visual indication of the shape of the output list. The backquote (`) introduces a template for a list. The elements of the template appear verbatim in the resulting list, except when they are prefixed by a comma (`,') or a comma-splice (`,@'). (For the purpose of illustration, we have written the verbatim elements of the template in UPPER-CASE.)

The comma and the comma-splice are used to insert the macro arguments into the template. The comma inserts the result of evaluating its following expression. The comma-splice inserts the result of evaluating its following expression after splicing it, ie, it removes the outermost set of parentheses. (This implies that an expression introduced by comma-splice must be a list.)

In our example, given the values that test and

branch are bound to, it is easy to see that the template will expand to the required

(IF (< (pressure tube) 60)

(BEGIN

(open-valve tube)

(attach floor-pump tube)

(depress floor-pump 5)

(detach floor-pump tube)

(close-valve tube)))

8.2 Avoiding variable capture inside macros

A two-argument disjunction form, my-or, could be defined as follows:

(define-macro my-or

(lambda (x y)

`(if ,x ,x ,y)))

my-or takes two arguments and returns the value of the first of them that is true (ie, non-#f). In particular, the second argument is evaluated only if the first turns out to be false.

(my-or 1 2)

=> 1

(my-or #f 2)

=> 2

There is a problem with the my-or macro as it is written. It re-evaluates the first argument if it is true: once in the if-test, and once again in the ``then'' branch. This can cause undesired behavior if the first argument were to contain side-effects, eg,

(my-or

(begin

(display "doing first argument")

(newline)

#t)

2)

displays "doing first argument" twice.

This can be avoided by storing the if-test result in a local variable:

(define-macro my-or

(lambda (x y)

`(let ((temp ,x))

(if temp temp ,y))))

This is almost OK, except in the case where the second argument happens to contain the same identifier temp as used in the macro definition. Eg,

(define temp 3)

(my-or #f temp)

=> #f

Surely it should be 3! The fiasco happens because the macro uses a local variable temp to store the value of the first argument (#f) and the variable temp in the second argument got captured by the temp introduced by the macro.

To avoid this, we need to be careful in choosing local variables inside macro definitions. We could choose outlandish names for such variables and hope fervently that nobody else comes up with them. Eg,

(define-macro my-or

(lambda (x y)

`(let ((+temp ,x))

(if +temp +temp ,y))))

This will work given the tacit understanding that +temp will not be used by code outside the macro. This is of course an understanding waiting to be disillusioned.

A more reliable, if verbose, approach is to use generated symbols that are guaranteed not to be obtainable by other means. The procedure gensym generates unique symbols each time it is called. Here is a safe definition for my-or using gensym:

(define-macro my-or

(lambda (x y)

(let ((temp (gensym)))

`(let ((,temp ,x))

(if ,temp ,temp ,y)))))

In the macros defined in this document, in order to be concise, we will not use the gensym approach. Instead, we will consider the point about variable capture as having been made, and go ahead with the less cluttered +-as-prefix approach. We will leave it to the astute reader to remember to convert these +-identifiers into gensyms in the manner outlined above.

8.3 fluid-let

Here is a definition of a rather more complicated macro, fluid-let (sec 5.2). fluid-let specifies temporary bindings for a set of already existing lexical variables. Given a fluid-let expression such as

(fluid-let ((x 9) (y (+ y 1)))

(+ x y))

we want the expansion to be

(let ((OLD-X x) (OLD-Y y))

(set! x 9)

(set! y (+ y 1))

(let ((RESULT (begin (+ x y))))

(set! x OLD-X)

(set! y OLD-Y)

RESULT))

where we want the identifiers OLD-X, OLD-Y, and RESULT to be symbols that will not capture variables in the expressions in the fluid-let form.

Here is how we go about fashioning a fluid-let macro that implements what we want:

(define-macro fluid-let

(lambda (xexe . body)

(let ((xx (map car xexe))

(ee (map cadr xexe))

(old-xx (map (lambda (ig) (gensym)) xexe))

(result (gensym)))

`(let ,(map (lambda (old-x x) `(,old-x ,x))

old-xx xx)

,@(map (lambda (x e)

`(set! ,x ,e))

xx ee)

(let ((,result (begin ,@body)))

,@(map (lambda (x old-x)

`(set! ,x ,old-x))

xx old-xx)

,result)))))

The macro's arguments are: xexe, the list of variable/expression pairs introduced by the fluid-let; and body, the list of expressions in the body of the fluid-let. In our example, these are ((x 9) (y (+ y 1))) and ((+ x y)) respectively.

The macro body introduces a bunch of local variables: xx is the list of the variables extracted from the variable/expression pairs. ee is the corresponding list of expressions. old-xx is a list of fresh identifiers, one for each variable in xx. These are used to store the incoming values of the xx, so we can revert the xx back to them once the fluid-let body has been evaluated. result is another fresh identifier, used to store the value of the fluid-let body. In our example, xx is (x y) and ee is (9 (+ y 1)). Depending on how your system implements gensym, old-xx might be the list (GEN-63 GEN-64), and result might be GEN-65.

The output list is created by the macro for our given example looks like

(let ((GEN-63 x) (GEN-64 y))

(set! x 9)

(set! y (+ y 1))

(let ((GEN-65 (begin (+ x y))))

(set! x GEN-63)

(set! y GEN-64)

GEN-65))

which matches our requirement.

3 MzScheme provides define-macro via the defmacro library. Use (require (lib "defmacro.ss")) to load this library.

[Go to first, previous, next page; contents; index]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有