为什么Haskell中的if表情不受欢迎?

为什么Haskell中的if表情不受欢迎?,haskell,pattern-matching,Haskell,Pattern Matching,这是一个我一直在思考的问题。如果语句是大多数编程语言(至少是我曾经使用过的语言)的主要部分,但在Haskell中,它似乎是相当不受欢迎的。我知道在复杂的情况下,Haskell的模式匹配比一堆ifs要干净得多,但是有什么真正的区别吗 举个简单的例子,拿一个自制版本的sum(是的,我知道它可能只是foldr(+)0): 第二个问题是,这些选项中哪一个被视为“最佳实践”,为什么?我的教授很久以前总是尽可能地使用第一种方法,我想知道这是他个人的偏好还是背后有什么原因。你的例子的问题不是if表达式,而是使

这是一个我一直在思考的问题。如果语句是大多数编程语言(至少是我曾经使用过的语言)的主要部分,但在Haskell中,它似乎是相当不受欢迎的。我知道在复杂的情况下,Haskell的模式匹配比一堆ifs要干净得多,但是有什么真正的区别吗

举个简单的例子,拿一个自制版本的sum(是的,我知道它可能只是
foldr(+)0
):


第二个问题是,这些选项中哪一个被视为“最佳实践”,为什么?我的教授很久以前总是尽可能地使用第一种方法,我想知道这是他个人的偏好还是背后有什么原因。

你的例子的问题不是
if
表达式,而是使用了部分函数,如
head
tail
。如果您尝试使用空列表调用其中任何一个,它将抛出异常

> head []
*** Exception: Prelude.head: empty list
> tail []
*** Exception: Prelude.tail: empty list
如果在使用这些函数编写代码时出错,则在运行时之前不会检测到错误。如果模式匹配出错,程序将无法编译

例如,假设您意外地切换了函数的
,然后
else
部分

-- Compiles, throws error at run time.
sum xs = if null xs then (head xs) + sum (tail xs) else 0

-- Doesn't compile. Also stands out more visually.
sum [] = x + sum xs
sum (x:xs) = 0

请注意,您的guards示例也存在同样的问题。

您的第一个版本,即您的教授首选的版本,与其他版本相比具有以下优势:

  • 未提及
    null
  • 列表组件是在模式中命名的,因此没有提到
    头部
    尾部
  • 我确实认为这一点被认为是“最佳实践”

    有什么大不了的?为什么我们要特别避免头和尾呢?每个人都知道这些函数不是全部的,所以我们会自动尝试确保涵盖所有的情况。[]上的模式匹配不仅比
    null xs
    更突出,编译器还可以检查一系列模式匹配的完整性。因此,具有完全模式匹配的惯用版本更容易掌握(对于训练有素的Haskell读者而言)并由编译器进行详尽的证明

    第二个版本略好于上一个版本,因为我们可以立即看到所有案例都得到了处理。尽管如此,在一般情况下,第二个等式的RHS可能更长,并且可能有一个where子句,其中有两个定义,最后一个可能类似于:

    where
       ... many definitions here ...
       head xs = ... alternative redefnition of head ...
    
    要绝对了解RHS的功能,必须确保通用名称没有被重新定义


    第三个版本是最差的一个,IMHO:a)第二个匹配未能解构列表,仍然使用head和tail。b) 这种情况比使用两个等式的等效表示法稍微复杂一些。

    每次调用
    null
    head
    tail
    都需要模式匹配。但答案中的第一个版本只进行一个模式匹配,并通过模式的命名组件重用其结果


    正因为如此,它才更好。但它也更直观、更可读。

    我认为这篇文章很好地回答了这个问题。问题是,布尔值在构造时就失去了所有语义。这使得它们成为bug的重要来源,也使代码更难理解。

    模式匹配优于一系列if-then-else语句,原因(至少)如下:

  • 它更具声明性
  • 它与sum类型交互良好
  • 模式匹配有助于减少代码中的“偶然复杂性”——也就是说,代码实际上更多地是关于实现细节,而不是程序的基本逻辑

    在大多数其他语言中,当编译器/运行时看到一串if-then-else语句时,它别无选择,只能按照程序员指定的顺序测试条件。但是模式匹配鼓励程序员更多地关注于描述应该发生什么而不是如何执行事情。由于Haskell中的值的纯度和不可变性,编译器可以考虑模式集合作为一个整体,并决定如何最好地实现它们。
    一个类比是C的
    switch
    语句。如果您转储各种switch语句的汇编代码,您将看到编译器有时会生成一个比较链/树,而在其他情况下,它会生成一个跳转表。程序员在这两种情况下使用相同的语法-编译器根据比较值选择实现。如果它们形成连续的值块,则使用跳转表方法,否则使用比较树。如果在比较值中检测到其他模式,这种关注点的分离允许编译器在将来实现更多的策略。

    在许多编程语言中,if语句是基本原语,而开关块之类的东西只是语法糖,使深入嵌套的if语句更容易编写

    哈斯克尔的做法正好相反。模式匹配是基本的原语,if表达式实际上就是模式匹配的语法糖。类似地,像
    null
    head
    这样的构造只是用户定义的函数,它们最终都是使用模式匹配实现的。因此,模式匹配是最根本的问题。(因此可能比调用用户定义函数更有效。)

    在很多情况下——比如你上面列出的那些——这只是风格的问题。编译器几乎可以肯定地将事情优化到所有版本的性能大致相同的程度。但通常[并非总是如此!]模式匹配会让您更清楚地了解您想要实现的目标

    (写一个if表达式非常容易
    where
       ... many definitions here ...
       head xs = ... alternative redefnition of head ...