在Common Lisp中为迭代创建一个宏

Joh*_*4th 2 lisp macros common-lisp

我试图通过创建一个简单的+=宏和iterate宏来练习在Common Lisp中创建宏.我已经设法+=轻松地创建宏,我在我的iterate宏中使用它,我有几个问题.当我尝试运行我的宏时,例如

(iterate i 1 5 1 (print (list 'one i)))
Run Code Online (Sandbox Code Playgroud)

(i控制变量在哪里,1是起始值,5是结束值,1是增量值).我收到SETQ: variable X has no value

 (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
        )
    )
)
Run Code Online (Sandbox Code Playgroud)

我已经尝试了多种不同的东西来修复它,,并且这个错误使我不确定问题是否与iterate+=.从我能说的+=工作正常.

Rai*_*wig 10

检查+=扩展以查找错误

您需要检查扩展:

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

CL-USER 4 > (macroexpand-1 '(+= a 1))
(SETF A (+ X Y))
T
Run Code Online (Sandbox Code Playgroud)

上面的宏扩展显示xy使用,这是错误.我们需要在宏函数中评估它们:

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

CL-USER 6 > (macroexpand-1 '(+= a 1))
(SETF A (+ A 1))
T
Run Code Online (Sandbox Code Playgroud)

上面看起来更好.请注意顺便说一句.该宏已经存在于标准的Common Lisp中.它被称为incf.

另请注意,您不需要它,因为iterate代码中不需要副作用.我们可以在+不设置任何变量的情况下使用该函数.

样式

您可能希望稍微调整一下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)))
Run Code Online (Sandbox Code Playgroud)

在Lisp中,第一部分 - 变量,开始,结束,步骤 - 通常写在列表中.例如,参见DOTIMES.这使得例如可以使其成为step可选项并为其赋予默认值:

(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)))
Run Code Online (Sandbox Code Playgroud)

让我们看看扩展,格式化以便于阅读.我们使用的函数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
Run Code Online (Sandbox Code Playgroud)

您可以看到创建的符号gensym也可以通过其名称识别.

我们也可以让Lisp格式化生成的代码,使用函数pprint并给出正确的余量.

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)))
Run Code Online (Sandbox Code Playgroud)