刚开始学习macro遇到一点小问题,现在要定义一个宏函数variable-chain,可以接收任意多个参数,实现如下效果
lisp
(setf a 0) (setf b 0) (setf c 0) (variable-chain a b c) > a b > b c > c 0
即(variable-chain a b c d)将a的值置为b的变量名,将b的值置为c的变量名,以此类推
一开始我写的代码如下:
lisp
(defmacro variable-chain (&rest vars) `(do ((v vars (rest v))) ((null (rest v)) nil) (setf (car v) (cadr v))))
运行出现错误提示vars没有值,找不到错误的原因望高手指点
我先来解释你遇到的问题y,然后再回答你最初的问题x
宏参数中的符号要在反引用中使用,需要用逗号进行求值,所以在vars前加一个逗号即可
(defmacro variable-chain (&rest vars)
`(do ((v ,vars (rest v)))
((null (rest v)) nil)
(setf (car v) (cadr v))))
我们看看宏展开后是什么样子
CL-USER> (macroexpand-1 '(variable-chain a b c))
(DO ((V (A B C) (REST V))) ((NULL (REST V)) NIL) (SETF (CAR V) (CADR V)))
T
当然,你的宏写的是有问题的,其实你真正想要的是rotatef
(let ((a 1)
(b 2)
(c 3))
(rotatef a b c)
(list a b c))
那么该如何写一个rotatef宏呢,写一个setf版本吧,(rotatef a b c)
等价于(setf tmp a a b b c c tmp)
,我们需要一个临时变量(不一定名字叫做tmp)
第一步我们先来生成a a b b c c
这种形式
(loop for sym in '(a b c) append (list ,sym ,sym))
==> (A A B B C C)
第二步我们需要连接临时变量,临时变量的名称不要直接写tmp,要用gensym动态生成
,@可以吞掉一层括号,将(A A B B C C)
变成A A B B C C
插入到setf里面
(defmacro our-rotatef (&rest args)
(let ((tmp (gensym)))
`(setf ,tmp ,@(loop for sym in args append (list sym sym)) ,tmp)))
看下生成的代码,#:G586是gensym动态生成的符号
CL-USER> (macroexpand-1 '(our-rotatef a b c))
(SETF #:G586 A A B B C C #:G586)
T
考虑到更人性化一点(our-rotatef)
如果直接被调用那不是变成(setf #:G586 #:G586)
了吗,由于#:G586前面还未赋值,这句直接执行是会出错的,出错信息有点莫名其妙
Unbound variable: #:G586
[Condition of type UNBOUND-VARIABLE]
加一个断言,让出错信息更明确一些
(defmacro our-rotatef (&rest args)
(assert (not (null args)) nil "(m)our-rotatef至少需要一个参数")
(let ((tmp (gensym)))
`(setf ,tmp ,@(loop for sym in args append (list sym sym)) ,tmp)))
我的我的,我看你用setf那句以为你是要赋值呢,要将a的值设为b的符号名,你需要对b进行quote,例如
(let (a b)
(setf a (quote b)))
我们心中有了模板之后,就从1扩展到N
butlast和cdr对列表(‘A A ’B B ‘C C)
掐头去尾,变成(A 'B B 'C)
(defmacro foo (&rest args)
`(setf ,@(butlast (cdr (loop for sym in args append `(',sym ,sym))))))
再次宏展开查看
CL-USER> (macroexpand-1 '(foo a b))
(SETF A 'B)
T
CL-USER> (macroexpand-1 '(foo a b c))
(SETF A 'B B 'C)
T
补充一点我的理解吧,有一些介绍LISP有7个基础操作符,利用这7个操作符可以写一个eval出来,也就是LISP实现了自举(一门语言用自己实现了自己)。在CommonLISP中,仅有这7个操作符是实现不了CommonLISP的,于是增加了更多的操作符,一共有25个(记不清了),被称为特殊操作符,这些操作符无法用宏或者函数来实现,必须是由编译器内置。
不清楚一个东西是宏、函数或者是特殊操作符的时候,可以笼统地称为过程,宏函数的叫法比较罕见