Lisp注释的通用格式如下:
- 四个分号用于对文件中整个子部分进行评论。
- 三个分号用于介绍单个过程。
- 两个分号用于描述以下行中的表达式/过程定义。
- 一个分号用于行末注释。
过程概述注释应该遵循RnRS文档的风格,因此,要将注释添加到您的过程中,可以像这样编写:
;;; 过程:display-n NUM ...
;; 按提供的顺序将每个参数输出到屏幕。
(define
display-n (lambda nums
(letrec ((display-n-inner (lambda (nums)
(display (car nums))
(if (not (equal? (cdr nums) '()))
(display-n-inner (cdr nums))))))
(display-n-inner nums))))
注意:我不使用三个分号来描述整个过程,因为这会破坏Emacs中的fill-paragraph。
现在谈谈代码,我会放弃整个变量定义为lambda的定义方式。是的,我知道这是定义函数的“最纯粹”方式,并且它与定义LET和其他过程的结果具有良好的一致性,但是有语法糖的原因,那就是使事情更易读。 LETREC也一样 - 只需使用内部DEFINE即可,这与使用LETREC相同,但更易读。
DISPLAY-N-INNER的参数称为NUMS并不是什么大问题,因为该过程很短,DISPLAY-N直接将其NUMS传递给它。但是,“DISPLAY-N-INNER”有点不好的命名方式。您可以为其指定具有更多语义含义的名称,或者为其指定一个简单的名称,例如“ITER”或“LOOP”。
现在谈谈过程的逻辑。首先,“(equal?(cdr nums)'())”很傻,最好改为“(null?(cdr nums))”。实际上,在操作整个列表时,最好将基本情况设为测试列表本身是否为空,而不是其CDR是否为空。这样,如果未传递任何参数,则该过程不会出错(除非您希望它这样做,但我认为让DISPLAY-N什么也不做更有意义)。此外,应该检查是否要停止过程,而不是是否要继续:
(define (display-n . nums)
(define (iter nums)
(if (null? nums)
#t ; 它返回什么都无所谓。
(begin (display (car nums))
(iter (cdr nums)))))
(iter nums))
但是,尽管如此,我认为该过程本身并不是完成其任务的最佳方法,因为它过于关注遍历列表的细节。相反,您将使用更抽象的FOR-EACH方法来执行工作。
(define (display-n . nums)
(for-each display nums))
这样做,读者不需要深入了解CAR和CDR的细节,只需理解FOR-EACH会显示NUMS中的每个元素。
nums
是由提供给函数的所有参数组成的列表。递归调用外部过程只会提供一个参数 - 以列表形式的参数。问题在于,display-n
实际上期望多个参数 - 不是一个列表参数,所以它不起作用。话虽如此,您的建议可以使用apply
实现 - 但我不确定apply
的效率如何,因此仍然可能不是一个好的解决方案。 - Camcpu time: 0 real time: 9 gc time: 0
,而f-apply需要cpu time: 77548 real time: 100984 gc time: 17148
。也许是我的代码中有些东西导致它运行如此之慢,但我不这么认为。apply在提供列表给函数之前是否实际迭代列表(如我上面所建议的)?假设那段代码没有错误,如果有人能解释一下apply
的实现方式(特别是在Racket中),那将有助于解释我的代码输出。 - Camapply
中新鲜地 cons 了可展开的参数列表。Common Lisp 标准不要求这种 consing(因此函数不应依赖于其 rest 参数被新鲜地 consed);我不确定 Scheme 标准。另一个问题是列表可能是循环的。我想这归结于 lambda 列表如何与提供的参数匹配。无论如何,每当您有这样的长参数列表时,通常会使用高阶函数,例如map
或reduce
。 - Svante