在lisp中,我想编写一个宏for循环,而不使用lisp中的循环函数,它模仿C或Java中的for循环。我想只用基本的 Lisp 语法来实现这一点。

2

  • loop?那就do吧。


    – 

  • Stackoverflow 不会为您编写代码。如果您遇到真正的编程问题并且可以向我们展示您到目前为止所尝试的内容,那就最好了。这听起来也像是一个家庭作业问题。


    – 


2 个回答
2

我怀疑这是一个家庭作业问题,所以这里有一个答案,如果你理解它,你可能已经学到了一些关于 Lisp 和编程的相当重要的知识。

(defmacro for ((var init while step) &body forms)
  (let ((<c> (make-symbol "C")))
    `((lambda (,<c>)
        (funcall ,<c> ,<c> ,init))
      (lambda (,<c> ,var)
        (when ,while
          ,@forms
          (funcall ,<c> ,<c> ,step))))))

现在

> (for (i 1 (< i 10) (1+ i))
  (print i))

1 
2 
3 
4 
5 
6 
7 
8 
9 
nil

这不是用 CL 编写循环结构的方式。它可能在计划中。

loopdo其他迭代宏通常扩展为原始特殊形式。

如果您想编写自己的迭代宏,那么最好了解一下如何loop在 Common Lisp 实现中进行扩展。下面是loopSBCL 中表单的扩展:

CL-USER> (macroexpand-1 '(loop for x from 1 to 10 do (print x)))
(BLOCK NIL
  (LET ((X 1))
    (DECLARE (IGNORABLE X)
             (TYPE (AND REAL NUMBER) X))
    (TAGBODY
     SB-LOOP::NEXT-LOOP
      (WHEN (> X '10) (GO SB-LOOP::END-LOOP))
      (PRINT X)
      (SB-LOOP::LOOP-DESETQ X (1+ X))
      (GO SB-LOOP::NEXT-LOOP)
     SB-LOOP::END-LOOP)))
T

实施for

作为编写宏的练习,这是很有趣的,但在实际代码中,您可能应该使用预先存在的迭代形式,例如loopdo,或使用诸如.这些解决方案经过测试、可靠,并且可以轻松执行 C 样式for循环可以执行的任何操作。

我非常有信心,下面的实现足够微妙,老师不会将其误认为是首先提出这个问题的学生的解决方案,但这里需要学习的东西可能会帮助学生。

使用 的基本实现非常简单,但是如果目标是模拟 C 样式循环,tagbody您可能还应该实现跳转语句,例如break和。这有点棘手,您可以通过几种方法来解决它。这是一种解决方案。我不保证此实现是最佳的或没有错误;将此视为一个起点:continuefor

(defmacro for ((var init) loop-cond step &body body)
  (let ((loop-again (gensym "LOOP-AGAIN"))
        (loop-increment (gensym "LOOP-INCREMENT"))
        (loop-end (gensym "LOOP-END")))
    (labels ((expand (b)
               (cond ((null b) nil)
                     ((atom b)
                      (case b
                        ((break :break) `(go ,loop-end))
                        ((continue :continue) `(go ,loop-increment))
                        (otherwise b)))
                     ((eq (first b) 'for) b) ; do not expand inside of FOR forms
                     (t
                      (cons (expand (first b))
                            (expand (rest b)))))))
      (let ((body (expand body)))
        `(let ((,var ,init))
           (tagbody
              ,loop-again
              (when (not ,loop-cond) (go ,loop-end))
              ,@body
              ,loop-increment
              (setf ,var ,step)
              (go ,loop-again)
              ,loop-end))))))

到这里,一个基本的tagbody形式就建立了。如果控制表达式为 false,则退出循环,否则执行循环体for,循环变量递增,并且控制跳转到循环开头重新开始。

为了实现break,首先通过遍历该代码来扩展continue循环体for,查找符号breakcontinue(或它们的关键字对应物以遵循宏的约定loop)的出现,并将这些出现替换为go跳转到适当的形式表单内的标签tagbody

请注意,Common Lisp 有一个用于调试的函数。上述实现不允许在主体中使用此函数,for因为任何出现的break都会扩展为go表单。出于演示目的,这并不重要,但更健壮的代码可能会选择处理这种可能性。一种解决方案是仅接受关键字参数:break:continue。在一个主体中同时拥有break:break可用于不同目的似乎会令人困惑for,因此更好的解决方案可能是将这些命令重命名为break-for和之类的名称continue-for

宏调用的扩展for会生成包含 go 标签的代码。如果您有嵌套for表单,则需要注意控制符号breakcontinue在正确的上下文中展开。也就是说,如果您在循环体中随意展开所有此类符号for,那么它们都会展开为go引用最外层循环的形式。为了避免这个问题,您需要for在遍历循环体时抑制形式的扩展for。当扩展内部宏调用时,这些内部形式的主体for将被正确扩展。for

当一个for宏形式被求值时,它会扩展成这样:

CL-USER> (macroexpand-1 '(for (x 0) (< x 10) (1+ x)
                          (when (evenp x) :continue)
                          (print x)))
(LET ((X 0))
  (TAGBODY
   #:LOOP-AGAIN404
    (WHEN (NOT (< X 10)) (GO #:LOOP-END406))
    (WHEN (EVENP X) (GO #:LOOP-INCREMENT405))
    (PRINT X)
   #:LOOP-INCREMENT405
    (SETF X (1+ X))
    (GO #:LOOP-AGAIN404)
   #:LOOP-END406))
T

以下是该宏的一些使用示例for

CL-USER> (for (x 0) (< x 5) (1+ x) (print x))

0 
1 
2 
3 
4 
NIL

CL-USER> (for (x 1) (< x 16) (* 2 x) (print x))

1 
2 
4 
8 
NIL

CL-USER> (for (x 1) (< x 10) (+ 1 x)
           (when (evenp x) :continue)
           (print x))

1 
3 
5 
7 
9 
NIL

CL-USER> (for (x 1) (< x 10) (+ 1 x)
           (when (evenp x) :continue)
           (when (> x 5) :break)
           (print x))

1 
3 
5 
NIL

CL-USER> (for (x 0) (< x 10) (1+ x)
           (for (y 0) (< y 10) (1+ y)
             (when (evenp y) :continue)
             (format t "(~A, ~A) " x y))
           (terpri))
(0, 1) (0, 3) (0, 5) (0, 7) (0, 9) 
(1, 1) (1, 3) (1, 5) (1, 7) (1, 9) 
(2, 1) (2, 3) (2, 5) (2, 7) (2, 9) 
(3, 1) (3, 3) (3, 5) (3, 7) (3, 9) 
(4, 1) (4, 3) (4, 5) (4, 7) (4, 9) 
(5, 1) (5, 3) (5, 5) (5, 7) (5, 9) 
(6, 1) (6, 3) (6, 5) (6, 7) (6, 9) 
(7, 1) (7, 3) (7, 5) (7, 7) (7, 9) 
(8, 1) (8, 3) (8, 5) (8, 7) (8, 9) 
(9, 1) (9, 3) (9, 5) (9, 7) (9, 9) 
NIL