函数式编程的适当注释

12

我一直在学习Scheme,我刚刚意识到我不知道如何正确地注释我的函数式Scheme代码。当然我知道怎样添加注释——你需要在;后面添加你的注释。我的问题是,我应该在我的注释中写些什么,以及应该在哪里注释才能最大化代码的可读性和易理解性呢?

以下是我写的一个代码片段。它是一个名为display-n的函数。它可以接受任意数量的参数,并按提供的顺序将每个参数输出到屏幕上。

(define display-n
  (lambda nums
    (letrec ((display-n-inner 
              (lambda (nums)
                (display (car nums))
                (if (not (equal? (cdr nums) (quote ()))
                    (display-n-inner (cdr nums))))))
      (display-n-inner nums))))

编辑:改进了制表符,并替换了'()(quote ()),以避免SO损坏格式。

我不确定如何/在哪里添加注释以使其更易于理解。我看到的一些Scheme代码只在顶部有注释,如果你想使用代码,那很好,但如果你想理解/修改它,这并不有用。

另外 - 我应该如何注释宏?

4个回答

6
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中的每个元素。


作为另一种中间形式,为什么要使用内部过程?您可以使外部递归而不会损失封装性。 - Svante
@Svante: 这是因为外部的 nums 是由提供给函数的所有参数组成的列表。递归调用外部过程只会提供一个参数 - 以列表形式的参数。问题在于,display-n 实际上期望多个参数 - 不是一个列表参数,所以它不起作用。话虽如此,您的建议可以使用 apply 实现 - 但我不确定 apply 的效率如何,因此仍然可能不是一个好的解决方案。 - Cam
@Svante:你能否解释一下这段代码的输出结果(http://pastebin.org/393648)?我很难理解为什么`apply`版本运行时间如此之长,尽管你所说的。输出结果似乎证实了我上面的“担忧”,但我不想轻率下结论。 - Cam
我尝试了一下if,除了我将L的长度从20k增加到了40k。现在f-no-apply需要cpu time: 0 real time: 9 gc time: 0,而f-apply需要cpu time: 77548 real time: 100984 gc time: 17148。也许是我的代码中有些东西导致它运行如此之慢,但我不这么认为。apply在提供列表给函数之前是否实际迭代列表(如我上面所建议的)?假设那段代码没有错误,如果有人能解释一下apply的实现方式(特别是在Racket中),那将有助于解释我的代码输出。 - Cam
我看了一下,至少 Racket 和 SBCL 在 apply 中新鲜地 cons 了可展开的参数列表。Common Lisp 标准不要求这种 consing(因此函数不应依赖于其 rest 参数被新鲜地 consed);我不确定 Scheme 标准。另一个问题是列表可能是循环的。我想这归结于 lambda 列表如何与提供的参数匹配。无论如何,每当您有这样的长参数列表时,通常会使用高阶函数,例如 mapreduce - Svante
显示剩余4条评论

5
一些随机注释:
传统上,Scheme和Lisp代码使用;;;作为顶级注释,;;作为代码中的注释,;作为与其在同一行的代码的注释。Emacs对此提供支持,将每个注释略微区分对待。但特别是在Scheme方面,这已不再像以前那样流行,但;;;之间的差异仍然很常见。
大多数现代Scheme都采用了新类型的注释:有:
  • #|...|#用于块注释——对于长文本非常有用,可以注释整个文件。
  • #;<expr>是一个注释,使实现忽略表达式,这对于调试非常有用。
  • 至于要写的实际内容,除了更加函数化的方法通常会让您在如何布置代码方面有更多选择之外,它与任何其他语言没有区别。它还使编写较小的函数更加方便,这些函数组合成较大的功能块——这也改变了文档风格,因为许多这样的小函数将是“自说明的”(它们易于阅读,并且非常明显地显示它们的工作方式)。
    我不想重复老生常谈,但我仍然认为您应该花一些时间阅读HtDP。它在其设计配方中鼓励先编写示例,然后编写文档,然后将其扩展到实际代码。此外,此配方使您拥有具有非常标准的注释集合的代码:输入/输出类型、目的语句、必要时有关函数如何实现的某些文档,示例可以视为另一种文档(在“真实”代码中将变成注释的代码)。 (还有其他书籍也采取类似的立场来记录文档。)
    最后,记录宏与记录任何其他代码没有区别。唯一可能非常不同的是注释中写的内容:而不是描述某个函数正在执行什么操作,您倾向于描述代码会扩展到什么程度,因此注释也更多地涉及元级别。宏的常见方法是在宏内部仅执行所需级别的最小工作(例如,在(lambda()...)中包装表达式),并将实际实现留给函数。这也有助于记录,因为两个相关的部分将具有有关宏如何扩展和运行的注释,独立于此。

    +1,谢谢 - 像往常一样有帮助。特别是我欣赏第二个和最后一个要点。 - Cam

    2

    我采用与此处发布的方法类似的方法:

    http://www.cc.gatech.edu/computing/classes/cs2360/ghall/style/commenting.html

    注意:这是针对Common Lisp的。

    具体而言:

    " Four Semicolons(;;;;)
    ...denote a sub heading in the file...
    
    Three Semicolons(;;;)
    ...denote a description of the succeeding function, macro, or
    variable definition...
    [I usually just most of the description into the "docstring"
      of the function or variable.] 
    
    
     Two Semicolons(;;)
     ...denote a description of the succeeding expression...
    
     One Semicolon(;)
     ...denotes an in-line comment that explains a particular element
        of the expression on that line... Brevity is important for
        inline comments"
    

    0

    我认为一个很好的开始是在函数开头加上一个简短的句子描述函数的作用。

    该函数可以使用任意数量的参数,并按照提供的顺序将每个参数输出到屏幕上。

    将其作为注释放在开头。

    我不太熟悉Scheme,所以无法评论(:-)是否根据正常的Scheme风格需要额外的逐行注释来解释函数如何实现这一结果(但我怀疑不需要)。


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