在lisp中,我想编写一个宏for循环,而不使用lisp中的循环函数,它模仿C或Java中的for循环。我想只用基本的 Lisp 语法来实现这一点。
2
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 编写循环结构的方式。它可能在计划中。
|
loop
和do
其他迭代宏通常扩展为原始特殊形式。
如果您想编写自己的迭代宏,那么最好了解一下如何loop
在 Common Lisp 实现中进行扩展。下面是loop
SBCL 中表单的扩展:
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
宏
作为编写宏的练习,这是很有趣的,但在实际代码中,您可能应该使用预先存在的迭代形式,例如loop
、do
,或使用诸如.这些解决方案经过测试、可靠,并且可以轻松执行 C 样式for
循环可以执行的任何操作。
我非常有信心,下面的实现足够微妙,老师不会将其误认为是首先提出这个问题的学生的解决方案,但这里需要学习的东西可能会帮助学生。
使用 的基本实现非常简单,但是如果目标是模拟 C 样式循环,tagbody
您可能还应该实现跳转语句,例如break
和。这有点棘手,您可以通过几种方法来解决它。这是一种解决方案。我不保证此实现是最佳的或没有错误;将此视为一个起点:continue
for
(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
,查找符号break
或continue
(或它们的关键字对应物以遵循宏的约定loop
)的出现,并将这些出现替换为go
跳转到适当的形式表单内的标签tagbody
。
请注意,Common Lisp 有一个用于调试的函数。上述实现不允许在主体中使用此函数,for
因为任何出现的break
都会扩展为go
表单。出于演示目的,这并不重要,但更健壮的代码可能会选择处理这种可能性。一种解决方案是仅接受关键字参数:break
和:continue
。在一个主体中同时拥有break
和:break
可用于不同目的似乎会令人困惑for
,因此更好的解决方案可能是将这些命令重命名为break-for
和之类的名称continue-for
。
宏调用的扩展for
会生成包含 go 标签的代码。如果您有嵌套for
表单,则需要注意控制符号break
和continue
在正确的上下文中展开。也就是说,如果您在循环体中随意展开所有此类符号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
|
loop
?那就用do
吧。–
–
|