In Functional Programming, is it considered a bad

2019-01-17 14:46发布

Is it generally considered a bad practice to use non-exhaustive pattern machings in functional languages like Haskell or F#, which means that the cases specified don't cover all possible input cases?

In particular, should I allow code to fail with a MatchFailureException etc. or should I always cover all cases and explicitly throw an error if necessary?

Example:

let head (x::xs) = x

Or

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

F# (unlike Haskell) gives a warning for the first code, since the []-case is not covered, but can I ignore it without breaking functional style conventions for the sake of succinctness? A MatchFailure does state the problem quite well after all ...

7条回答
孤傲高冷的网名
2楼-- · 2019-01-17 14:59

Explicit is better than implicit (borrowed from the Zen of Python ;))

It's exactly the same as in a C switch over an enum... It's better to write all the cases (with a fall through) rather than just putting a default, because the compiler will tell you if you add new elements to the enumeration and you forgot to handle them.

查看更多
虎瘦雄心在
3楼-- · 2019-01-17 15:08

If you complete your pattern-matchings with a constructor [] and not the catch-all _, the compiler will have a chance to tell you to look again at the function with a warning the day someone adds a third constructor to lists.

My colleagues and I, working on a large OCaml project (200,000+ lines), force ourselves to avoid partial pattern-matching warnings (even if that means writing | ... -> assert false from time to time) and to avoid so-called "fragile pattern-matchings" (pattern matchings written in such a way that the addition of a constructor may not be detected) too. We consider that the maintainability benefits.

查看更多
可以哭但决不认输i
4楼-- · 2019-01-17 15:11

The Haskell prelude (standard functions) contains many partial functions, e.g. head and tail only work on non-empty lists, but don't ask me why.

查看更多
Melony?
5楼-- · 2019-01-17 15:15

This question has two aspects.

  1. For the user of the API, failwith... simply throws a System.Exception, which is unspecific (and therefore is sometimes considered a bad practice in itself). On the other hand, the implicitly thrown MatchFailureException can be specifically caught using a type test pattern, and therefore is preferrable.
  2. For the reviewer of the implementation code, failwith... clearly documents that the implementer has at least given some thought about the possible cases, and therefore is preferrable.

As the two aspects contradict each other, the right answer depends on the circumstances (see also kvb's answer). A solution which is 100% "correct" from any point of view would have to

  • deal with every case explicitly,
  • throw a specific exception where necessary, and
  • clearly document the exception

Example:

/// <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
查看更多
Bombasti
6楼-- · 2019-01-17 15:18

Non-exhaustive pattern machings are idiomatic in Haskell but extremely bad style in F# (and OCaml, Standard ML etc.).

Exhaustiveness checking is a very valuable way to catch errors at compile time in strict languages like F# but lazy languages like Haskell often use infinite lazy lists where the empty list cannot arise so they (historically) chose brevity over statically-checked correctness.

查看更多
时光不老,我们不散
7楼-- · 2019-01-17 15:21

This is a special case of a more general question, which is "should you ever create partial functions". Incomplete pattern matches are only one example of partial functions.

As a rule total functions are preferable. When you find yourself looking at a function that just has to be partial, ask yourself if you can solve the problem in the type system first. Sometimes that is more trouble than its worth (e.g. creating a whole type of lists with known lengths just to avoid the "head []" problem). So its a trade-off.

Or maybe you just asking whether its good practice in partial functions to say things like

head [] = error "head: empty list"

In which case the answer is YES!

查看更多
登录 后发表回答