【问题标题】:creating a macro for iterate in Common Lisp在 Common Lisp 中创建用于迭代的宏
【发布时间】:2018-10-30 03:28:16
【问题描述】:

我正在尝试通过创建一个简单的 += 宏和一个 iterate 宏来练习在 Common Lisp 中创建宏。我已经设法轻松地创建了+= 宏,并且我在我的iterate 宏中使用它,我遇到了一些问题。当我尝试使用例如

运行我的宏时
(iterate i 1 5 1 (print (list 'one i)))

(其中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
        )
    )
)

我尝试了多种不同的方法来解决它,弄乱了,,这个错误让我不确定问题出在iterate 还是+=。据我所知,+= 工作正常。

【问题讨论】:

  • 您的+= 宏是incf 的不卫生克隆。 (incf x delta)x 位置增加delta。然而,与您的+= 不同,incf 只会评估一次x。此外,您有一个错误; setf 赋值右侧的 x 名称是硬编码的,因为它位于带引号的列表中。如果我们使用(+= a b),则生成的代码是(setf a (quote (+ x y))。您可以使用(macroexpand '(+= a b)) 进行检查。

标签: macros lisp common-lisp


【解决方案1】:

检查 += 扩展以查找错误

你需要检查扩展:

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

CL-USER 4 > (macroexpand-1 '(+= a 1))
(SETF A (+ X Y))
T

上面的宏展开显示使用了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

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

另请注意,您不需要它,因为在您的 iterate 代码中不需要副作用。我们可以直接使用+函数而不设置任何变量。

风格

您可能需要对 Lisp 风格进行更多调整:

  • no 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 中,第一部分——变量、开始、结束、步骤——通常写在一个列表中。参见例如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)))

让我们看看扩展,为了可读性而格式化。我们使用函数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创建的符号也可以通过它们的名称来识别。

我们也可以让 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)))

【讨论】:

  • @Rainer_Joswig 这是一个编写宏的不错的小教程。谢谢!
【解决方案2】:

我想通了。原来我的 += 宏和迭代宏中的其他几个地方有问题。这是最终的工作结果。我在编写 += 宏时忘记了 ,。其他宏声明出现问题。

 (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
        )
    )

)

【讨论】:

    猜你喜欢
    • 2012-10-19
    • 1970-01-01
    • 2018-09-06
    • 2014-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-18
    相关资源
    最近更新 更多