在功能编程中,将不完整的模式匹配视为不好的做法

时间:2009-12-10 16:49:18

标签: haskell f# functional-programming

在Haskell或F#等函数式语言中使用非详尽的模式机制通常被认为是不好的做法,这意味着指定的案例并未涵盖所有可能的输入案例?

特别是,我应该允许代码以MatchFailureException等方式失败,还是应该始终覆盖所有情况并在必要时明确抛出错误?

示例:

let head (x::xs) = x

或者

let head list = 
    match list with
    | x::xs -> x
    |    _  -> failwith "Applying head to an empty list"

F#(与Haskell不同)给出了第一个代码的警告,因为[] - 案例没有被覆盖,但为了简洁起见,我可以忽略它而不破坏功能样式约定吗?一旦MatchFailure确实很好地陈述了问题...

7 个答案:

答案 0 :(得分:45)

如果使用构造函数[]完成模式匹配而不是全能_,编译器将有机会告诉您再次查看该函数,并在当天发出警告有人在列表中添加了第三个构造函数。

我的同事和我,在一个大型OCaml项目(200,000多行)上工作,强迫我们避免部分模式匹配警告(即使这意味着不时写| ... -> assert false)并避免如此 - 称为“fragile pattern-matchings”(模式匹配以这样的方式编写,即可能无法检测到构造函数的添加)。我们认为可维护性有益。

答案 1 :(得分:9)

显式优于隐式(借用Python的Zen;))

与在enum上的C切换完全相同...最好编写所有情况(通过掉落)而不是仅仅放置default,因为编译器将告诉您是否在枚举中添加了新元素,而忘记处理它们。

答案 2 :(得分:5)

我认为这在很大程度上取决于背景。您是在尝试编写健壮,易于调试的代码,还是在尝试编写简单易懂的内容?

如果我正在与多个开发人员一起开展长期项目,我会在断言中添加一个更有用的错误消息。我同意Pascal的评论,从软件工程的角度来看,不使用通配符是理想的。

如果我正在开展一个规模较小的项目,我是唯一的开发者,我不会再考虑使用不完整的匹配。如有必要,您可以随时检查编译器警告。

我认为这也取决于你所匹配的类型。实际上,没有额外的联合案例会添加到列表类型中,因此您不必担心脆弱的匹配。另一方面,在您控制并正在积极处理的代码中,可能存在不断变化的类型并且添加了额外的联合案例,这意味着防止脆弱匹配可能是值得的。

答案 3 :(得分:5)

这是一个更普遍的问题的特例,“你应该创造部分功能吗”。不完整模式匹配只是部分函数的一个例子。

通常,总函数是优选的。当你发现自己正在寻找一个只需要部分的功能时,问问自己是否可以先在类型系统中解决问题。有时这比它的价值更麻烦(例如,创建一个已知长度的整个类型的列表只是为了避免“head []”问题)。所以这是一个权衡。

或者你可能只是问一下在部分功能方面的优秀做法是否可以说

head [] = error "head: empty list"

在这种情况下答案是肯定的!

答案 4 :(得分:4)

Haskell前奏(标准函数)包含许多部分函数,​​例如头尾只能在非空列表上工作,但不要问我为什么。

答案 5 :(得分:2)

这个问题有两个方面。

  1. 对于API的用户,failwith ...只会抛出一个非特定的System.Exception(因此有时被认为是一种不好的做法)。另一方面,隐式抛出的MatchFailureException可以使用类型测试模式专门捕获,因此是可取的。
  2. 对于实施代码的审核者,failwith ...明确说明实施者至少已经考虑过可能的情况,因此是可取的。
  3. 由于这两方面相互矛盾,正确的答案取决于具体情况(另见kvb的回答)。从任何观点来看,100%“正确”的解决方案都必须

    • 明确处理每一个案件,
    • 在必要时抛出特定的例外,
    • 明确记录例外情况

    示例:

    /// <summary>Gets the first element of the list.</summary>
    /// <exception cref="ArgumentException">The list is empty.</exception>
    let head list = 
        match list with
        | [] -> invalidArg "list" "The list is empty." 
        | x::xs -> x
    

答案 6 :(得分:-1)

非详尽的模式机制在Haskell中是惯用的,但在F#(和OCaml,标准ML等)中风格非常糟糕。

穷举检查是一种非常有价值的方法,可以在编译时以严格的语言(如F#)捕获错误,但是像Haskell这样的惰性语言经常使用无限的懒惰列表,其中空列表不会出现,因此它们(历史上)选择简洁而不是静态检查的正确性。