7

最近,我一直在花一些空闲时间来尝试将注意力集中在 Haskell 和一般的函数式编程上。我的主要疑虑之一是(并且仍然是)我认为简洁的函数式表达式(无论是在 Haskell 还是任何其他语言中)的不可读性。

这是一个例子。我刚刚在一些工作代码中进行了以下转换:

def scrabble_score(word, scoretable):    
    score = 0
    for letter in word:
        if letter in scoretable:
            score += scoretable[letter]
    return score

def scrabble_score(word, scoretable):
    return sum([scoretable.get(x, 0) for x in word])

后者写起来更令人满意(而且,请记住,我自己就是写它的人,读起来也是如此)。它更短更甜,并且没有声明任何讨厌的变量,如果我在输入代码时犯了任何错误,这些变量可能会令人头疼。(顺便说一句,我意识到我可以get()在命令式函数中使用 dict 的方法,但我在执行此转换时意识到了这一点,所以我在示例中保留了它。)

我的问题是:尽管如此,后一个版本的代码真的比前一个版本更具可读性吗?与以前的版本相比,它似乎是一个更单一的表达方式,必须一次全部理解,在以前的版本中,你有机会从更小、更原子的片段中构建块的含义。这个问题来自于我对试图解码所谓易于理解的 Haskell 函数的挫败感,以及我作为一年级 CS 学生的助教坚持认为我们编写代码的主要原因之一(经常被忽视)是为了交流与其他程序员/计算机科学家的想法。我很紧张,简洁的风格带有只写代码的味道,因此违背了交流和理解代码的目的。

4

3 回答 3

5

我的问题是:尽管如此,后一个版本的代码真的比前一个版本更具可读性吗?这似乎是一个更单一的表达方式,必须立即全部摸索

我会说它更具可读性,尽管不一定是易读的选项。是的,这在一行中更具逻辑性,但这被三个事实所掩盖。

首先,您仍然可以在精神上将其分解为更小的部分,并在组合它们之前单独理解它们。我立即看到你在总结一些东西(在这个阶段我不必知道在总结什么)。我还看到您从scoretablefor each获取条目letter,如果不存在则默认为 0(在此阶段,我不必知道该列表随后会发生什么)。这些东西可以独立识别和理解。其次,有些独立的部分很少,可能有两三个。这不是不合理的理解,当数十个关注点并排放置时,“单体”方面更成问题。

最后,第一个版本的所谓优势,

与以前的版本相比,您有机会从更小、更原子的片段中构建块的含义。

也是一个问题:当我开始阅读时,我看到了一个 for 循环。这很好,但它实际上并没有告诉我任何事情(除了有一个循环,它不能帮助我理解逻辑,只有它的实现)。因此,我必须牢记这一事实并继续阅读以解决此循环的含义。在某种程度上,任何代码都是如此,但代码越多,情况就越糟糕。同样,这score = 0条线本身并不意味着任何东西,它只是一个很小的实现细节,没有人需要知道才能理解该函数通过将每个单词的字母的分数相加来计算单词的拼字游戏分数。

在阅读命令式循环时,必须跟踪多个可变变量、控制流等以弄清楚发生了什么,并且必须将各个部分拼凑在一起。此外,它只是要阅读更多的字符,因此即使理解是即时的,也需要更长的时间来理解。您必须考虑这些成本。通过让每一行变得更简单可能会获得的任何东西都会被这种效果所迷惑。

也就是说,以下版本可以说更干净,正是因为它将算法的两个部分分成单独的行:

def scrabble_score(word, scoretable):
    letter_scores = [scoretable.get(letter, 0) for letter in word]
    return sum(letter_scores)

但是差异很小,这是相当主观的。两者都很好,比你的第一个版本好得多。(哦,另一件事:我会使用生成器表达式而不是列表推导,我只保留列表推导以最小化噪音。)

于 2013-08-20T22:55:06.687 回答
1

我认为简洁的风格很好,因为你可以在像这样的情况下自由地扩展它,而不会在控制结构上浪费大量的垂直空间,比如第一个 if 。例如

def scrabble_score(word, scoretable):
    # local function to look up a score in the table
    def get_score(x): return scoretable.get(x,0)

    scores = map(get_score, word)  # convert word into a list of scores

    return sum(scores)

我发现这是最易读的,尽管它需要多几行。我认为这是声明式的。

于 2013-08-20T21:56:13.197 回答
1

这些东西主要是口味问题。应该记住,品味本身就是一个教育和经验的问题——你会发现更长的命令式代码更具可读性,因为这是你唯一学会阅读的东西(到目前为止)。

我发现您的第二种形式更具可读性,因为我习惯了功能样式的形式,并且我了解其中的所有元素。我经常发现简洁的 haskell 代码难以理解,因为我还没有学习所有的运算符。

在我看来,实际的解决方案是:(a)使用注释(b)避免任何过于聪明的事情(c)解释你选择做的任何聪明的事情(d)简洁和不缩进通常比大量和深度缩进更容易阅读;(e) 记住你的听众。

将这些原则应用于这种情况:在 python 中,您会期望可以通过简单的函数操作表达的东西以这种方式表达,因此您的听众应该对此感到满意。在任何情况下,您的代码都需要注释来说明您确实(或不)打算默默地忽略无效(未评分)字符。

于 2013-08-20T21:48:32.937 回答