common lisp中once-only 宏

1 practical common lisp

1.1 once-only macro explanation

:   (defmacro once-only ((&rest names) &body body)
:     (let ((gensyms (loop for n in names collect (gensym))))
:       `(let (,@(loop for g in gensyms collect `(,g (gensym))))
:         `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
:           ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
:              ,@body)))))

:    (defmacro t5 ((a) &body body)
:   `(let ((x1 ,a))
:      (format t "x1: ~a~%" x1)
:      `(let ((x2 (+ ,x1 ,,a)))
:         (format t "x2: ~a~%" x2)
:         ,(let ((x3 (+ x1 5)))
:               (format t "X3: ~a~%" x3)))))

:   (defmacro t55 (x)
:     (t5 (x)))

先看t5的执行情况,可以看出,没有`范围的代码,是在macroexpan-1后出结果

   : CL-USER>   (macroexpand-1 '(t5 (100)))
   :  (LET ((X1 100))
   :   (FORMAT T "x1: ~a~%" X1)
   :   (LIST* 'LET (LIST* (LIST (LIST* 'X2 (LIST (LIST* '+ (LIST* X1 (LIST 100))))))
   :                      (LIST* '(FORMAT T "x2: ~a~%" X2)
   :                             (LIST (LET ((X3 (+ X1 5)))
   :                                     (FORMAT T "X3: ~a~%" X3)))))))




可以看出,有一个`的范围内,在执行(eval (macroexpand-1后出结果

:   CL-USER> (eval  (macroexpand-1 '(t5 (100))))
:   x1: 100
:   X3: 105
:   (LET ((X2 (+ 100 100))) (FORMAT T "x2: ~a~%" X2) NIL)

可以看出,在(eval和(eval (macroexpand-1的结果一样的

:   CL-USER>   (eval '(t5 (100)))
:   x1: 100
:   X3: 105
:   (LET ((X2 (+ 100 100))) (FORMAT T "x2: ~a~%" X2) NIL)

总结:对于macro代码的阅读,可以按照“`”来分级数,一个"`"表示加一级,而“,”表示减一级。每一级需要(eval一次。

可以看出,有两个``范围的内的代码,在执行(eval (eval (macroexpand-1后出结果

:   CL-USER> (eval (eval  (macroexpand-1 '(t5 (100)))))
:   x1: 100
:   X3: 105
:   x2: 200
:   NIL

defmcro嵌套的级数对应于eval的次数。

:   CL-USER> (t55 10)
:   x1: 10
:   X3: 15
:   x2: 20
:   NIL

once-only的分解

:   (defmacro once-only ((&rest names) &body body)
:     (let ((gensyms (loop for n in names collect (gensym))))
:       `(let (,@(loop for g in gensyms collect `(,g (gensym))))
:         `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
:           ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
:              ,@body)))))

假设代码为 (once-only (a) (list 'list a))

:     (let ((gensyms (loop for n in names collect (gensym))))

这一行在macroexpand-1时已经当做代码执行了,并产生了gensyms 假设为 #:G386

:       `(let (,@(loop for g in gensyms collect `(,g (gensym))))

这一行在macroexpan后,产生代码(let ((#:G386 (gensym)))) ;; ,g 表示g要替换为g变量的值,也就是gensyms中的元素

:         `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))

这一行是要第二次eval后才能产生代码的,,,g替换为了,g的值,即#:G386, ,,n替换为了入参的变量名

:           ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
:              ,@body)

这一段用了一个","在(let前面,把两级的"`"变为了一级,所以第一次eval后就产生了代码(list (let ((a #:G386)) (list 'list a)) 以及body中的",n"和",g"等变量都会替换其值,产生代码

所以,macroexpand后的代码为:

: (LET ((#:G386 (GENSYM)))
:    (LIST* 'LET (LIST* (LIST (LIST* #:G386 (LIST A)))
:       (LIST (LET ((A #:G386))
:         (LIST 'LIST A))))))

第一次eval时,产生了第二轮变量名 假设为 #:G391

: (let ((,g (gensym))))
#:G386 = #:G391
:         `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))


产生代码,(let ((#:G391 nval))) 这里,,g为第二轮产生的 (gensym), nval为names传进来的值

,n = ,g = ,,g的名字

: (let ((,n ,g)))

所以第一次eval后的代码为:

: (LET ((#:G391 11)) (LIST #:G391))

第二次eval后即为结果:(11)

书中说编写macro的macro,这个需要两次eval是对应的,因为每次macroexpand-1时,对于没有"`"的代码是会直接执行的。

总结出来macro解析的规律为:

  1. 每次调用macro时,相当于 (eval (macroexpand-1 (some-macro)))
  2. 每次调用macroexpand-1时,对于未"`"的代码是直接运行,并对"`"中的代码产生替换;对于"`"一级的代码,直接产生 (eval)后可以出结果的代码,对于"``"中的代码,产生两次(eval (eval))后能够产生代码的代码。对于有两次eval才能产生代码的macro,一般应用于编写macro的macro,因为每次macroexpand-1都会eval一次没有"`"的代码 (这种macro不能包含在"`"里面) 。对于,var的变量,会把,var替换为新的变量名。例如: (defmacro name1 (x)`(list ,x)) (macroexpand-1 '(name1 a)) 会把,x换位a。
  3. 调用eval时,会把expand后的代码做一次运行。对于变量,不会再有,var,也就不再替换变量名,只会把变量换为变量的值。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值