函数定义的组成部分

通过上述分析,我们得到了函数定义的基本框架: 首先,需要一个变量 total 用于记录石子总数,它将作为函数的返回值。

其次,函数需要一个参数,表示三角形的总行数,可以命名为 number-of-rows

最后,需要一个变量作为计数器。可以命名为 counter,但更合适的名称是 row-number。因为该计数器在函数中用于计数行数,程序应尽可能写得易于理解。

当 Lisp 解释器开始执行函数内的表达式时,total 的初始值应设为 0,因为此时尚未累加任何数值。随后函数应依次将第一行、第二行、第三行……的石子数加入总数,直到所有行累加完毕。

totalrow-number 只在函数内部使用,因此可以用 let 声明为局部变量并赋予初始值。显然 total 初始值应为 0,row-number 初始值应为 1,因为从第一行开始计数。对应的 let 语句如下:

  (let ((total 0)
        (row-number 1))
    body...)

在声明内部变量并绑定初始值后,就可以开始编写 while 循环。作为判断条件的表达式,只要 row-number 小于或等于 number-of-rows 就应返回真 t。(如果条件仅判断行数小于总行数,最后一行将永远不会被计入总数;因此条件必须是小于或等于。)

Lisp 提供了 <= 函数,当第一个参数的值小于或等于第二个参数时返回真,否则返回假。因此 while 的判断条件应写为:

(<= row-number number-of-rows)

石子总数可以通过反复将当前行石子数加到已有总数得到。由于每行石子数等于行数,因此只需将行数累加到总数即可。(显然在更复杂的场景中,每行石子数与行数的关系可能更复杂,此时只需将行数替换为对应表达式即可。)

(setq total (+ total row-number))

该表达式的作用是将 total 的新值设为原有总数与当前行石子数之和。

在更新 total 之后,需要为下一轮循环(如果存在)设置条件。这通过递增作为计数器的 row-number 变量实现。row-number 递增后,while 循环开头的真假条件会再次判断其是否仍小于等于总行数,如果是,则将新的行号累加到上一轮循环得到的总数中。

Emacs Lisp 内置函数 1+ 用于将数字加 1,因此 row-number 可通过如下表达式递增:

(setq row-number (1+ row-number))