15
votes

In Erlang, you are encouraged not to match patterns that you do not actually handle. For example:

case (anint rem 10) of
    1 -> {ok, 10}
    9 -> {ok, 25}
end;

is a style that is encouraged, with other possible results resulting in a badmatch result. This is consistant with the "let it crash" philosophy in Erlang.

On the other hand, F# would issue an "incomplete pattern matching" in the equivalent F# code, like here.

The question: why wouldn't F# remove the warning, effectively by augmenting every pattern matching with a statement equivalent to

|_ -> failwith "badmatch"

and use the "let it crash" philosophy?

Edit: Two interesting answers so far: either to avoid bugs that are likely when not handling all cases of an algebraic datatype; or because of the .Net platform. One way to find out which is to check OCaml. So, what is the default behaviour in OCaml?

Edit: To remove misunderstanding by .Net people who have no background in Erlang. The point of the Erlang philosophy is not to produce bad code that always crashes. Let it crash means let some other process fix the error. Instead of writing the function so that it can handle all possible cases, let the caller (for example) handle the bad cases which are thrown automatically. For those with Java background, it is like the difference between having a language with checked exceptions which must declare everything it will possibly return with every possible exception, and having a language in which functions may raise exceptions that are not explicitly declared.

5
I think the philosophy is that your function should handle the entire domain expected of it, and the compiler reminds you of this. If the function doesn't handle any more cases than two then you should restrict the arguments by type, otherwise account for all of them. That is my understanding; I'm learning about this philosophy myself.grettke
@grettke: you are correct. This philosophy is the exact opposite of the Erlang philosophy. And my question is why is this philosophy preferred (or when). The answers below are quite illuminating.Muhammad Alkarouri

5 Answers

24
votes

F# (and other languages with pattern matching, like Haskell and O'Caml) does implicitly add a case that throws an exception.

In my opinion the most valuable reason for having complete pattern matches and paying attention to the warning, is that it makes it easy to refactor by extending your datatype, because the compiler will then warn you about code you haven't yet updated with the new case.

On the other hand, sometimes there genuinely are cases that should be left out, and then it's annoying to have to put in a catch-all case with what is often a poor error message. So it's a trade-off.

In answer to your edit, this is also a warning by default in O'Caml (and in Haskell with -Wall).

13
votes

In most cases, particularly with algebraic datatypes, forgetting a case is likely to be an accident and not an intentional decision to ignore a case. In strongly typed functional languages, I think that most functions will be total, and should therefore handle every case. Even for partial functions, it's often ideal to throw a specific exception rather than to use a generic pattern matching failure (e.g. List.head throws an ArgumentException when given an empty list).

Thus, I think that it generally makes sense for the compiler to warn the developer. If you don't like this behavior, you can either add a catch-all pattern which itself throws an exception, or turn off or ignore the warning.

11
votes

why wouldn't F# remove the warning

Interesting that you would ask this. Silently injecting sources of run-time error is absolutely against the philosophy behind F# and its relatives. It is considered to be a grotesque abomination. This family of languages are all about static checking, to the extent that the type system was fundamentally designed to facilitate exactly these kinds of static checks.

This stark difference in philosophy is precisely why F# and Python are so rarely compared and contrasted. "Never the twain shall meet" as they say.

So, what is the default behaviour in OCaml?

Same as F#: exhaustiveness and redundancy of pattern matches is checked at compile time and a warning is issued if a match is found to be suspect. Idiomatic style is also the same: you are expected to write your code such that these warnings do not appear.

This behaviour has nothing to do with .NET and, in fact, this functionality (from OCaml) was only implemented properly in F# quite recently.

For example, if you use a pattern in a let binding to extract the first element of a list because you know the list will always have at least one element:

let x::_ = myList

In this family of languages, that is almost always indicative of a design flaw. The correct solution is to represent your non-empty list using a type that makes it impossible to represent the empty list. Static type checking then proves that your list cannot be empty and, therefore, guarantees that this source of run-time errors has been completely eliminated from your code.

For example, you can represent a non-empty list as a tuple containing the head and the tail list. Your pattern match then becomes:

let x, _ = myList

This is exhaustive so the compiler is happy and does not issue a warning. This code cannot go wrong at run-time.

I became an advocate of this technique back in 2004, when I refactored about 1kLOC of commercial OCaml code that had been a major source of run-time errors in an application (even though they were explicit in the form of catch-all match cases that raised exceptions). My refactoring removed all of the sources of run-time errors from most the code. The reliability of the entire application improved enormously. Moreover, we had wasted weeks hunting bugs via debugging but my refactoring was completed within 2 days. So this technique really does pay dividends in the real world.

5
votes

Erlang cannot have exhaustive pattern matching because of dynamic types unless you have a catch-all in every, which is just silly. Ocaml, on the other hand, can. Ocaml also tries to push all issues that can be caught at compile-time to compile-time.

3
votes

OCaml by default does warn you about incomplete matches. You can disable it by adding "p" to the "-w" flag. The idea with these warnings is that more often than not (at least in my experience) they are an indication of programmer error. Especially when all your patterns are really complex like Node (Node (Leaf 4) x) (Node y (Node Empty _)), it is easy to miss a case. When the programmer is sure that it cannot go wrong, explicitly adding a | _ -> assert false case is an unambiguous way to indicate that.

GHC by default turns off these warnings; but you can enable it with -fwarn-incomplete-patterns