首页 > common lisp定义一个宏函数

common lisp定义一个宏函数

刚开始学习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个(记不清了),被称为特殊操作符,这些操作符无法用宏或者函数来实现,必须是由编译器内置。
不清楚一个东西是宏、函数或者是特殊操作符的时候,可以笼统地称为过程,宏函数的叫法比较罕见

【热门文章】
【热门文章】