CLisp 18:定义宏的宏,确定参数的前缀

实现宏时,最难理解的是反引号、逗号、和@符号,这里不重复基本的知识,而是介绍最难得部分“在宏里面定义宏”。即调用一个宏时,它会定义一个新的宏。一些例子中,会在宏参数前加逗号、引号、和@的复杂组合,怎么理解这些组合呢?这里用实验的方法来研究它们的作用,而不管什么规则、原理。先枚举各种组合:

'x:引号

x:没有符合

,x:逗号

,,x:两个逗号

',x:引号逗号

,',x:逗号引号逗号

',',x:引号逗号引号逗号

,,@x:逗号逗号 @

,@,x:逗号 @逗号

 

实验用的测试代码如下,写到一个文件中,然后loadCLisp环境中

(defmacro m0 (x)

  `(defmacro g0 ()

     `(print 'x)))

 

(defmacro m1 (x)

  `(defmacro g1 ()

     `(print x)))

 

(defmacro m2 (x)

  `(defmacro g2 ()

     `(print ,x)))

 

(defmacro m3 (x)

  `(defmacro g3 ()

     `(print ,,x)))

 

(defmacro m4 (x)

  `(defmacro g4 ()

     `(print ',x)))

 

(defmacro m5 (x)

  `(defmacro g5 ()

     `(print ,',x)))

 

(defmacro m6 (x)

  `(defmacro g6 ()

     `(print ',',x)))

 

(defmacro m7 (x)

  `(defmacro g7 ()

     `(print ,,@x)))

 

(defmacro m8 (x)

  `(defmacro g8 ()

     `(print ,@,x)))

 

对宏m0 ~ m6 ,执行 (macroexpand-1 '(m0 abc)) 看结果

m0  (DEFMACRO G0 NIL '(PRINT 'X)) ;

m1  (DEFMACRO G1 NIL '(PRINT X)) ;

m2  (DEFMACRO G2 NIL (LIST 'PRINT X)) ;

m3  (DEFMACRO G3 NIL (LIST 'PRINT ABC)) ;

m4  (DEFMACRO G4 NIL (LIST 'PRINT (LIST 'QUOTE X))) ;

m5  (DEFMACRO G5 NIL '(PRINT ABC)) ;

m6  (DEFMACRO G6 NIL '(PRINT 'ABC)) ;

看以看到,参数x前面有两个逗号的,x全部被换成abc;参数x前面只有一个逗号或没有逗号的,仍然是x。这是展开外层宏时的规律。

 

对宏m0 ~ m6 ,执行 (m0 abc) 生成宏g0 ~ g6,然后对宏g0 ~ g6执行(macroexpand-1 '(g0)) 看结果

g0  (PRINT 'X) ;

g1  (PRINT X) ;

g2  LET*: variable X has no value

g3  LET*: variable ABC has no value

g4  LET*: variable X has no value

g5  (PRINT ABC) ;

g6  (PRINT 'ABC) ;

展开g2g3g4时报错了,需要提供xabc的值,说明展开内层宏时会做参数替换。提供xabc的值后再看结果

(setq x 'x-value)

(setq abc 'abc-value)

g2  (PRINT X-VALUE) ;

g3  (PRINT ABC-VALUE) ;

g4  (PRINT 'X-VALUE) ;

 

再来执行宏g0 ~ g0,看打印的结果,其中g2g3报错了,要去提供x-valueabc-value的值。实际上,从g0 ~ g6的展开结果就可以猜出下面结果。

g0  x

g1  x-value

g2  SYSTEM::READ-EVAL-PRINT: variable X-VALUE has no value

g3  SYSTEM::READ-EVAL-PRINT: variable ABC-VALUE has no value

g4  X-VALUE

g5  ABC-VALUE

g6  ABC

 

总结一下结果,x是宏的形参,abc是执行外层宏提供给x的实参。

'x    :打印x

x     :打印x-value

,x    :打印x-value-value

,,x   :打印abc-value-value

',x   :打印x-value

,',x  :打印abc-value

',',x :打印abc

 

测试时将x作为print的参数,如果将x放在函数的位置,即

(defmacro m0 (x)

  `(defmacro g0 ()

      (符号组合x ...)))

则变成

'x    :非法,不能执行('x ...),因为不允许将'x作为函数名

x     :执行内层宏时执行函数x

,x    :展开内层宏时执行函数x,执行内层宏时执行函数x-value

,,x   :展开内层宏时执行函数abc,执行内层宏时执行函数abc-value

',x   :展开内层宏时执行函数x

,',x  :执行内层宏时执行函数abc

',',x :非法,不能执行('abc ...)

 

实际使用一下上面的分析结果,实现On-Lisp书上的缩略语的例子。大致样子如下,还没有确定参数long的前缀

(defmacro abbrev (short long)

  `(defmacro ,short (&rest args)

     `(long ,@args)))

期望的结果是,执行(abbrev df defun)后可以用(df ...)作为(defun ...)的缩略语。首先,参数long必须被替换成defun,因此前面必须有两个逗号,可以选356,排除0124。前面已经分析6会变成('defun ...),不能执行,因此排除6,只剩下35。我们想直接执行defun,而不是将defun看成指向另一个函数的变量(执行defun指向的函数),因此排除3,选择5。即产生long的前缀是,',(逗号引号逗号)。

 

分析到这里,忽然觉得测试代码不够符合实际。在展开g2g4时,报错要去提供x的值,我们弄了一个全局变量x,这不符合实际,应该以宏参数的形式提供x的值,即给宏g2g4增加一个参数x。另外,总结结果时,总说“执行函数”导致

理解困难。为了对比,我们给g0 ~ g6都增加参数x,重新分析。修改后的测试代码如下:

(defmacro m0 (x)

  `(defmacro g0 (x)

     `(print 'x)))

对宏m0 ~ m6 ,执行 (m0 abc) 生成宏g0 ~ g6,然后对宏g0 ~ g6执行(macroexpand-1 '(g0 xyz)) 看结果

g0  (PRINT 'X) ;

g1  (PRINT X) ;

g2  (PRINT XYZ) ;

g3  (PRINT ABC-VALUE) ;

g4  (PRINT 'XYZ) ;

g5  (PRINT ABC) ;

g6  (PRINT 'ABC) ;

 

再来看一下宏abbrev,执行(abbrev df defun)生成宏df,执行(df ...)期望其等效于(defun ...)。和前面进行的测试进行对比,x变成longabc变成defundefun直接出现在最后的结果中,和g5对应上了。现在有了一个简单易行的方法,来确定两个反引号中各参数的前缀。假设用abc作为实参调用外层宏,用xyz作为实参调用内层宏,abc的值为abc-value,期望放参数的地方最终被替换成什么呢,拿你的期望和上面的测试结果对比,找对应上的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值