首先,您需要确定newTriangle 在做什么。调用(print (newTriangle 3)) 表明newTriangle 应该返回一个字符串,然后通过调用print 将其打印出来。但是,newTriangle 的 OP 定义既是打印输出,也是将三角形的单行作为字符串返回。
对newTriangle 的递归调用永远不会到达,因为在到达此行之前,k 的值的所有可能情况都已用尽。由于k 只能小于零、等于零或大于零,并且由于所有这些情况都在到达递归调用之前进行了测试,因此永远不会到达。另请注意,cond 语句的最后部分的 OP 代码语法错误。 cond 分支中的第一个表达式是一个测试,约定在这里使用t 表示一个分支,如果到达该分支总是会被评估。但是,这里不需要这么多情况。
假设newTriangle 函数不应该返回一个字符串,而是应该打印一个三角形作为副作用,它应该怎么做?如果输入的数字大于 0,它应该打印一行字符数等于输入数字的行,然后在输入减一的情况下调用自己;否则它什么都不做:
(defun print-triangle (k)
(when (> k 0)
(princ (make-string k :initial-element #\a))
(terpri)
(print-triangle (- k 1))))
这个定义被命名为print-triangle 以强调它打印一个三角形作为副作用,因为 kebab-case 在 Lisps 中是惯用的,而 camelCase 不是。请注意,每次在输入大于零的情况下调用 print-triangle 时,都会打印出一个正确长度的字符串,然后打印一个换行符(使用名称模糊的 terpri,它只是将换行符写入当前输出流),然后再次调用print-triangle,k 减 1。
REPL 交互示例:
CL-USER> (print-triangle 3)
aaa
aa
a
NIL
如果目标是返回一个字符串,一种方法是调用一个辅助函数,将结果保存在一个参数中:
(defun new-triangle (k)
(build-triangle k ""))
(defun build-triangle (k result)
(if (> k 0)
(build-triangle (- k 1)
(concatenate 'string
result
(make-string k :initial-element #\a)
(string #\newline)))
result))
这里,new-triangle 接受一个整数参数,并调用 build-triangle,在 result 位置传递整数参数和一个空字符串。 build-triangle 函数的操作与之前的print-triangle 非常相似,但不是打印行,而是将它们与result 以及包含换行符的字符串连接起来。当build-triangle 完成时,result 字符串返回到new-triangle。请注意,简单地从 REPL 调用 new-triangle 将打印结果字符串 as data(即,带引号);在new-triangle 的结果上调用print 将把字符串打印为数据,并返回字符串。要查看打印的字符串不带引号,可以使用format;或者你可以使用princ,它将打印不带引号的字符串,并返回字符串本身:
CL-USER> (new-triangle 3)
"aaa
aa
a
"
CL-USER> (print (new-triangle 3))
"aaa
aa
a
"
"aaa
aa
a
"
CL-USER> (format t "~A" (new-triangle 3))
aaa
aa
a
NIL
CL-USER> (princ (new-triangle 3))
aaa
aa
a
"aaa
aa
a
"