Macros 在Common Lisp中创建用于迭代的宏

Macros 在Common Lisp中创建用于迭代的宏,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,我试图通过创建一个简单的+=宏和一个迭代宏来练习在Common Lisp中创建宏。我已经很容易地创建了+=宏,并且在我的迭代宏中使用了它,我遇到了一些问题。当我尝试运行宏时,例如 (iterate i 1 5 1 (print (list 'one i))) (其中,i是控制变量,1是起始值,5是结束值,1是增量值)。我收到SETQ:变量X没有值 (defmacro += (x y) (list 'setf x '(+ x y))) (defmacro iterate (

我试图通过创建一个简单的
+=
宏和一个
迭代
宏来练习在Common Lisp中创建宏。我已经很容易地创建了
+=
宏,并且在我的
迭代
宏中使用了它,我遇到了一些问题。当我尝试运行宏时,例如

(iterate i 1 5 1 (print (list 'one i)))
(其中,
i
是控制变量,
1
是起始值,
5
是结束值,
1
是增量值)。我收到
SETQ:变量X没有值

 (defmacro += (x y)
        (list 'setf x '(+ x y)))


(defmacro iterate (control beginExp endExp incExp &rest body)
    (let ( (end (gensym)) (inc (gensym)))
        `(do ( (,control ,beginExp (+= ,control ,inc)) (,end ,endExp) (,inc ,incExp) )
            ( (> ,control ,end) T)
            ,@ body
        )
    )
)

我尝试了多种不同的方法来修复它,方法是弄乱
,而这个错误使我无法确定问题是在
迭代
还是
+=
中。据我所知,
+=
工作正常。

我找到了答案。原来我的+=宏和迭代宏中的其他几个地方都有问题。这是最终的工作结果。在编写+=宏时,我忘记了
。其他的宏观偏差是无序的

 (defmacro += (x y)
        `(setf ,x (+ ,x ,y)))


(defmacro iterate2 (control beginExpr endExpr incrExpr &rest bodyExpr)
    (let ((incr(gensym))(end(gensym)) )
        `(do ((,incr ,incrExpr)(,end ,endExpr)(,control ,beginExpr(+= ,control ,incr)))
            ((> ,control ,end) T)
            ,@ bodyExpr
        )
    )

)

检查
+=
扩展以查找错误

您需要检查扩展:

CL-USER 3 > (defmacro += (x y)
              (list 'setf x '(+ x y)))
+=

CL-USER 4 > (macroexpand-1 '(+= a 1))
(SETF A (+ X Y))
T
上面的宏展开显示使用了
x
y
,这就是错误。 我们需要在宏函数中评估它们:

CL-USER 5 > (defmacro += (x y)
              (list 'setf x (list '+ x y)))
+=

CL-USER 6 > (macroexpand-1 '(+= a 1))
(SETF A (+ A 1))
T
上面看起来更好。请注意,该宏已存在于标准公共Lisp中。它被称为
incf

还要注意的是,您不需要它,因为在
迭代
代码中不需要副作用。我们可以只使用
+
函数而不设置任何变量

风格

您可能需要对Lisp样式进行更多调整:

  • 没有camelCase->默认读取器不区分大小写
  • 说出变量名->提高可读性
  • 宏/函数中的文档字符串-提高可读性
  • GENSYM
    接受参数字符串->提高生成代码的可读性
  • 没有悬空的括号和括号之间没有空格->使代码更加紧凑
  • 更好的自动缩进->提高可读性
  • 正文用
    &body
    标记,而不是用
    &rest
    ->改进了使用
    iterate
  • do
    不需要
    +=
    宏来更新迭代变量,因为
    do
    更新变量本身->不需要任何副作用,我们只需要计算下一个值
  • 一般来说,编写一个好的宏要比编写一个普通函数花费更多的时间,因为我们是在元级别上进行代码生成编程的,需要考虑的问题更多,还有一些基本的缺陷。所以,慢慢来,重新阅读代码,检查扩展,编写一些文档
应用于您的代码,它现在看起来如下所示:

(defmacro iterate (variable start end step &body body)
  "Iterates VARIABLE from START to END by STEP.
For each step the BODY gets executed."
  (let ((end-variable  (gensym "END"))
        (step-variable (gensym "STEP")))
    `(do ((,variable ,start (+ ,variable ,step-variable))
          (,end-variable ,end)
          (,step-variable ,step))
         ((> ,variable ,end-variable) t)
       ,@body)))
在Lisp中,第一部分变量start end step通常写在一个列表中。参见示例
DOTIMES
。例如,这使得可以将
步骤
设置为可选,并为其提供默认值:

(defmacro iterate ((variable start end &optional (step 1)) &body body)
  "Iterates VARIABLE from START to END by STEP.
For each step the BODY gets executed."
  (let ((end-variable  (gensym "END"))
        (step-variable (gensym "STEP")))
    `(do ((,variable ,start (+ ,variable ,step-variable))
          (,end-variable ,end)
          (,step-variable ,step))
         ((> ,variable ,end-variable) t)
       ,@body)))
让我们看一下扩展,格式化为可读性。我们使用函数
macroexpand-1
,它只进行一次宏扩展,而不是对生成的代码进行宏扩展

CL-USER 10 > (macroexpand-1 '(iterate (i 1 10 2)
                               (print i)
                               (print (* i 2))))
(DO ((I 1 (+ I #:STEP2864))
     (#:END2863 10)
     (#:STEP2864 2))
    ((> I #:END2863) T)
  (PRINT I)
  (PRINT (* I 2)))
T
您可以看到,
gensym
创建的符号也可以通过名称识别

我们还可以使用函数
pprint
让Lisp格式化生成的代码,并给出一个右边距

CL-USER 18 > (let ((*print-right-margin* 40))
               (pprint
                (macroexpand-1
                 '(iterate (i 1 10 2)
                    (print i)
                    (print (* i 2))))))

(DO ((I 1 (+ I #:STEP2905))
     (#:END2904 10)
     (#:STEP2905 2))
    ((> I #:END2904) T)
  (PRINT I)
  (PRINT (* I 2)))

您的
+=
宏是
incf
的不卫生克隆<代码>(增量x增量)
x
位置增量为
delta
。但是,与您的
+=
不同,
incf
只计算
x
一次。而且,你有一个bug在里面;
setf
赋值右侧的
x
名称是硬编码的,因为它位于带引号的列表中。如果我们使用
(+=ab)
,生成的代码是
(setf a(quote(+xy))
。您可以使用
(macroexpand'(+=ab))
@Rainer\u Joswig检查这是编写宏的一个很好的小教程。谢谢!