理解Common Lisp do宏的语法

10
(do ((n 0 (1+ n))
     (cur 0 next)
     (next 1 (+ cur next)))
    ((= 10 n) cur)))

这是一段来自Lisp教科书的示例,介绍了关键字"do"

"do"的基本模板如下:

(do (variable-definitions*)
    (end-test-form result-form*)
 statement*)

然而,对于这个例子,我不清楚哪部分是哪部分。还有,中间的两行代码是做什么的?

谢谢!

4个回答

30
(do ((n 0 (1+ n))  ;declares n, initially 0, n+1 each subsequent iteration)
     (cur 0 next)   ;declares cur, initially 0, then old value of next
     (next 1 (+ cur next))) ;declares next, initially 1, then the sum of (the old) cur and next
    ((= 10 n) ;end condition (ends when n = 10)
     cur)    ; return value
  ;empty body
  )

翻译成类C代码

for(n=0, cur=0, next=1 ;
    !(n == 10) ;
    n=old_n+1, cur=old_next, next = old_cur + old_next)
{
    //do nothing 
    old_n = n;
    old_cur = cur;
    old_next = next;
}
return cur;

顺带一提,你应该能够看到这段代码返回第10个斐波那契数。


可选的EBNF/正式语法:

根据Hyperspec的语法是:

(do ({var | (var [init-form [step-form]])}*) 
    (end-test-form result-form*) 
    declaration* 
    {tag | statement}*)

理解这需要对EBNF有一定的了解,并对Hyperspec掌握相当多的知识


1
展示C语言翻译的好主意,使用“old_”变量模拟并行赋值!只是挑刺一下:你的Lisp代码对齐有误,并且多了一个闭合括号;你的C代码缺少结束分号。 :) - Will Ness
看这个翻译,可以说do宏更像命令式编程,而不是函数式编程吗? - h__
1
@hyh 是和否 --- Common Lisp 是多范式的,这是一个更新变量的迭代构造,因此它肯定是命令式的。然而,这种形式返回一个值,因此您可以将此循环用作返回值,或者作为 if 语句中的条件(即 (if (> (this-fib-loop) 10) 'gt-10 'lte-10) ),这更具有函数式的特点。 - tobyodavies

14

您良好的缩进清晰地显示了每个部分:

(do ((n 0 (1+ n))
    ^(cur 0 next)
    |(next 1 (+ cur next)))
    |
    +-- first argument of do

    ((= 10 n) cur)))
    ^
    |
    +-- start of second argument of do

看,它们排列得很整齐,内部材料也有缩进:

   ((n 0 (1+ n))
    (cur 0 next)
    (next 1 (+ cur next)))
    ^
    |
    +- inner material of argument: three forms which are
       indented by 1 character and aligned together.

你的do语句没有第三个参数:循环体中没有语句(空循环)。


1
有时候,为了更好地理解,可以通过以下两种方式来处理表单:1. 对表单进行注释;2. 在主体中打印当前值,如下所示:
(do
 ;; varlist
 ((n 0 (1+ n))
  (cur 0 next)
  (next 1 (+ cur next)))
 ;; endlist
 ((= 10 n) cur)
  ;; body
  (print (list n cur next)))

这将打印

(0 0 1) 
(1 1 1) 
(2 1 2) 
(3 2 3) 
(4 3 5) 
(5 5 8) 
(6 8 13) 
(7 13 21) 
(8 21 34) 
(9 34 55) 
55

这应该能澄清问题。@_@

0
(do ((n 0 (1+ n))
     (cur 0 next)
     (next 1 (+ cur next)))
    ((= 10 n) cur))

do有3个部分。

  1. 变量
  2. 终止条件
  3. 主体

在这个特定的例子中,没有主体。所有真正的工作都由1和2完成。首先它设置了3个变量并给出了初始值和步骤形式。例如,n设置为0,在每次迭代期间它会进一步步进:(1+n),这将增加n

终止条件是((= n 10) cur):当n等于10时。然后将cur作为此do表达式的整个返回值返回。

将所有这些组合起来,在这个do示例中,它将从1加到10,得到55。


2
它是result-form-n而不是action-n,另外你的第二个代码块缩进得很糟糕。 - tobyodavies
1
你错过了很多括号。此外,这个计算序列(cur,next) = (0,1) (1,1) (1,2) (2,3) (3,5) (5,8) (8,13) ...的斐波那契数,而不仅仅是部分和。 - Will Ness

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接