F Sharp Programming/Pattern Matching Basics

Pattern matching is used for control flow; it allows programmers to look at a value, test it against a series of conditions, and perform certain computations depending on whether that condition is met. While pattern matching is conceptually similar to a series of if ... then statements in other languages, F#'s pattern matching is much more flexible and powerful.

It's possible to chain together multiple conditions which return the same value. For example:

>letgreetingname=matchnamewith|"Steve"|"Kristina"|"Matt"->"Hello!"|"Carlos"|"Maria"->"Hola!"|"Worf"->"nuqneH!"|"Pierre"|"Monique"->"Bonjour!"|_->"DOES NOT COMPUTE!";;valgreeting:string->string>greeting"Monique";;valit:string="Bonjour!">greeting"Pierre";;valit:string="Bonjour!">greeting"Kristina";;valit:string="Hello!">greeting"Sakura";;valit:string="DOES NOT COMPUTE!"

Pattern matching is such a fundamental feature that F# has a shorthand syntax for writing pattern matching functions using the function keyword:

letsomething=function|test1->value1|test2->value2|test3->value3

It may not be obvious, but a function defined in this way actually takes a single input. Here's a trivial example of the alternative syntax:

letgetPrice=function|"banana"->0.79|"watermelon"->3.49|"tofu"->1.09|_->nan(* nan is a special value meaning "not a number" *)

Although it doesn't appear as if the function takes any parameters, it actually has the type string -> float, so it takes a single string parameter and returns a float. You call this function in exactly the same way that you'd call any other function:

F#'s pattern matching syntax is subtly different from "switch statement" structures in imperative languages, because each case in a pattern has a return value. For example, the fib function is equivalent to the following C#:

Pattern matching is not a fancy syntax for a switch structure found in other languages, because it does not necessarily match against values, it matches against the shape of data.

F# can automatically bind values to identifiers if they match certain patterns. This can be especially useful when using the alternative pattern matching syntax, for example:

letrecfactorial=function|0|1->1|n->n*factorial(n-1)

The variable n is a pattern. If the factorial function is called with a 5, the 0 and 1 patterns will fail, but the last pattern will match and bind the value to the identifier n.

Note to beginners: variable binding in pattern matching often looks strange to beginners, however it is probably the most powerful and useful feature of F#. Variable binding is used to decompose data structures into component parts and allow programmers to examine each part; however, data structure decomposition is too advanced for most F# beginners, and the concept is difficult to express using simple types like ints and strings. This book will discuss how to decompose data structures using pattern matching in later chapters.

Note that F#'s pattern matching works from top to bottom: it tests a value against each pattern, and returns the value of the first pattern which matches. It is possible for programmers to make mistakes, such as placing a general case above a specific (which would prevent the specific case from ever being matched), or writing a pattern which doesn't match all possible inputs. F# is smart enough to notify the programmer of these types of errors.

F# will throw an exception if a pattern isn't matched. The obvious solution to this problem is to write patterns which are complete.

On occasions when a function genuinely has a limited range of inputs, its best to adopt this style:

letapartmentPricesnumberOfRooms=matchnumberOfRoomswith|1->500.0|2->650.0|3->700.0|_->failwith"Only 1-, 2-, and 3- bedroom apartments available at this complex"

This function now matches any possible input, and will fail with an explanatory informative error message on invalid inputs (this makes sense, because who would rent a negative 42 bedroom apartment?).

Example With Unmatched Patterns

>letgreetingname=matchnamewith|"Steve"->"Hello!"|"Carlos"->"Hola!"|_->"DOES NOT COMPUTE!"|"Pierre"->"Bonjour";;|"Pierre"->"Bonjour";;------^^^^^^^^^stdin(22,7):warningFS0026:Thisrulewillneverbematched.valgreeting:string->string

Since the pattern _ matches anything, and since F# evaluates patterns from top to bottom, its not possible for the code to ever reach the pattern "Pierre".

Here is a demonstration of this code in fsi:

>greeting"Steve";;valit:string="Hello!">greeting"Ino";;valit:string="DOES NOT COMPUTE!">greeting"Pierre";;valit:string="DOES NOT COMPUTE!"

The first two lines return the correct output, because we've defined a pattern for "Steve" and nothing for "Ino".

However, the third line is wrong. We have an entry for "Pierre", but F# never reaches it. The best solution to this problem is to deliberately arrange the order of conditions from most specific to most general.

Note to beginners: The code above contains an error, but it will not throw an exception. These are the worst kinds of errors to have, much worse than an error which throws an exception and crashes an app, because this error puts our program in an invalid state and silently continues on its way. An error like this might occur early in a program's life cycle, but may not show its effects for a long time (it could take minutes, days, or weeks before someone notices the buggy behavior). Ideally, we want buggy behavior to be as "close" to its source as possible, so if a program enters an invalid state, it should throw an exception immediately. To prevent this sort of problem it is usually a good idea to set the compiler flag that treats all warnings as errors; then the code will not compile thus preventing the problem right at the beginning.