【问题标题】:Proper commenting for functional programming函数式编程的正确注释
【发布时间】:2011-03-14 22:16:23
【问题描述】:

我一直在学习方案,我才意识到我真的不知道如何正确地注释我的功能方案代码。我当然知道如何添加评论 - 您添加 ; 并在其后添加评论。我的问题是 我应该在我的 cmets 中添加什么,我应该在哪里评论以使其他程序员阅读我的代码以获得最大的可读性和可理解性?

这是我编写的代码 sn-p。这是一个名为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 弄乱格式。

我只是不确定如何/在哪里添加 cmets 以使其更易于理解。我见过的一些方案代码顶部只有 cmets,如果您想使用代码,这很好,但如果您想理解/修改它,则无济于事。

另外 - 我应该如何评论宏?

【问题讨论】:

    标签: functional-programming coding-style comments scheme readability


    【解决方案1】:

    我认为一个很好的起点是用一句话描述函数的作用

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

    作为开头的评论。

    我对方案不是特别精通,因此我无法评论 (:-) 根据正常方案样式是否可以预期额外的逐行 cmets 解释函数如何实现该结果的机制(但我怀疑不是)。

    【讨论】:

      【解决方案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"
      

      【讨论】:

        【解决方案3】:

        一些随机笔记:

        • 传统上,Scheme 和 Lisp 代码使用 ;;; 表示顶级 cmets,;; 表示代码中的 cmets,; 表示与他们正在评论的代码在同一行的 cmets。 Emacs 对此提供了支持,对每一个的处理方式都略有不同。但特别是在 Scheme 方面,这已不再像以前那样流行,但 ;; 和 ; 之间的区别仍然很常见。

        • 大多数现代方案都采用了新的 cmets:theres:

          • #|...|# 用于块注释 - 对于对整个文件进行注释的长文本片段很有用。
          • #;<expr> 是使实现忽略表达式的注释,这对于调试很有用。
        • 至于写什么的实际内容,这与任何其他语言都没有什么不同,除了使用更实用的方法,您通常可以在如何布局代码方面有更多选择。它还可以更方便地编写组合成更大功能的较小功能 - 这也改变了文档样式,因为许多这样的小功能将是“自我记录”(因为它们易于阅读并且非常很明显它们是如何工作的)。

        • 我讨厌听起来像是破唱片,但我仍然认为您应该花一些时间在 HtDP 上。它在设计方案中鼓励的一件事是先编写示例,然后编写文档,然后将其扩展为实际代码。此外,这个秘籍为您提供了具有一组非常标准的 cmets 的代码:输入/输出类型、目的声明、一些有关如何在必要时实现函数的文档,并且示例可以被视为另一种文档(这将变成“真实”代码中的注释代码)。 (还有其他书籍在文档方面采取类似的立场。)

        • 最后,记录宏与记录任何其他代码没有什么不同。唯一可能与 cmets 中写的内容有很大不同的是:您倾向于描述它扩展的代码,而不是描述某个函数正在做什么,因此 cmets 也更多地在元等级。宏的一种常见方法是尽量减少宏内部的工作——正是该级别所需要的(例如,将表达式包装在(lambda () ...) 中),并将实际实现留给函数。这也有助于记录,因为两个相关的部分将有关于宏如何扩展以及如何独立运行的 cmets。

        【讨论】:

        • +1,谢谢 - 像往常一样有帮助。我特别欣赏第二点和最后一点。
        【解决方案4】:

        Lisp cmets 常用的样式是

        • 四个分号用于对文件的整个小节进行评论。
        • 三个分号用于介绍单个过程。
        • 两个分号用于描述下一行中的表达式/过程定义。
        • 一个分号用于结束注释。

        过程概述 cmets 可能应该遵循 RnRS 文档的样式,因此要按原样将 cmets 添加到您的过程中,看起来像

        ;;;程序:显示-n NUM ... ;;按照提供的顺序将每个参数输出到屏幕。 (定义 显示-n (lambda nums (letrec ((display-n-inner (lambda (nums) (显示(汽车号码)) (if (not (equal? (cdr nums) '())) (display-n-inner (cdr nums)))))) (display-n-inner nums))))

        注意我没有在整个过程描述中使用三个分号,因为它搞砸了 Emacs 中的填充段落。


        现在关于代码,我将放弃整个将变量定义为 lambda 的东西。是的,我知道这是定义函数的“最纯粹”的方式,它与定义过程保持良好的一致性是 LET 和其他过程的结果,但是语法糖是有原因的,它是为了让事情更多可读。 LETREC 也一样——只需使用内部的 DEFINE,它是一样的,但更具可读性。

        DISPLAY-N-INNER 的参数被称为 NUMS 并不是什么大不了的事,因为过程非常短,而且 DISPLAY-N 无论如何都只是将其 NUMS 直接交给它。不过,“DISPLAY-N-INNER”有点蹩脚的名字。你会给它一些更语义化的东西,或者给它一个简单的名字,比如“ITER”或“LOOP”。

        现在关于过程的逻辑。首先,(equal? (cdr nums) '()) 很傻,不如(null? (cdr nums))。实际上,当您对整个列表进行操作时,最好将基本情况作为列表本身而不是其 CDR 是否为空的测试。这样,如果您不向其传递任何参数,该过程就不会出错(除非您希望它这样做,但我认为 DISPLAY-N 如果什么也没得到,do 什么都不做更有意义)。此外,您应该测试是否停止该过程,而不是是否继续:

        (定义(显示-n . nums) (定义(迭代次数) (如果(空?数字) #t;它返回什么并不重要。 (开始(显示(汽车编号)) (iter (cdr nums))))) (迭代次数))

        但尽管如此,我会说过程本身并不是完成任务的最佳方式,因为它过于关注遍历列表的细节。相反,您将使用更抽象的 FOR-EACH 方法来完成这项工作。

        (定义(显示-n . nums) (对于每个显示数字))

        这样,程序的读者不会陷入 CAR 和 CDR 的细节中,他可以理解 FOR-EACH 将显示 NUMS 的每个元素。

        【讨论】:

        • +1,所有这些都是很好的建议(尤其是关于语法糖的部分)。另外,我实际上不知道null? - 所以这很有用。关于 for-each 我意识到这是实际解决这个特定问题的最佳方法,但是我的代码 sn-p 就太简单了:)
        • 作为另一种中间形式,为什么要使用内部过程呢?您可以在不丢失封装的情况下进行外部递归。
        • @Svante:这是因为外部的nums 是一个由提供给函数的所有参数组成的列表。递归调用外部过程只会提供一个参数——列表形式的参数。那里的问题是 display-n 实际上需要多个参数 - 而不是一个参数是列表,所以它不起作用。话虽这么说,您的建议可以使用apply - 除非我不确定apply 的效率如何,所以这可能仍然不是一个好的解决方案。
        • @Svante:就像我说的,我不确定 apply 的效率如何。如果需要 O(1) 时间,那当然没问题。在这种情况下,如果解释器意识到函数的参数无论如何都将参数“转换”回列表,那么肯定有可能花费 O(1) 时间。但是我担心它可能会在整个列表上运行apply,然后使用这些参数调用函数,最后将参数转换回列表。
        • @incrediman: apply 只是应用该函数。它是最基本的操作之一。你的恐惧根本没有根据。
        猜你喜欢
        • 2020-09-02
        • 1970-01-01
        • 1970-01-01
        • 2016-11-20
        • 2017-01-11
        • 1970-01-01
        • 1970-01-01
        • 2019-12-08
        • 2021-01-26
        相关资源
        最近更新 更多