Pattern matching is the act of checking one or more inputs against a pre-defined pattern and seeing if they match. In Elm, there’s only a fixed set of patterns we can match against, so pattern matching has limited application. However, it’s still an important feature of the language. In this section, we’ll go through a few examples of pattern matching to understand how it works in Elm.
Pattern Matching in Case Expression
case expression works by matching an expression to a pattern. When a match is found, it evaluates the expression to the right of
-> and returns whatever value is produced. We saw a simple example of pattern matching in the Case Expression section.
In this example,
dayInNumber is the expression and the pattern is the numbers
6, with a catchall
_ at the end. Here’s a slightly more complex example:
The code above shows the implementation of the
Result.map2 function which accepts three arguments: a function and two results. The
map2 function uses the
case expression to determine what to do based on what’s inside the result arguments.
Case #1: If both results are
Ok, it applies the function to the payloads contained inside each
Case #2: If the first result contains
Err, the second result gets ignored through the use of
_ and the
Err from the first result gets returned.
Case #3: If the second result contains
Err, the first result gets ignored, and the
Err from the second result gets returned.
Elm allows us to reach into a data structure and match patterns directly instead of writing nested
case expressions to get to the values. This enables us to write compact yet readable code.
- Learning From The Standard Library
- One of the best ways to learn Elm is to read Elm’s standard library. A large portion of the code contained in the standard library is written in Elm. It’s good code written by experienced programmers some of whom were responsible for creating Elm itself.
Just pick a module that interests you and browse through some of the functions, types, and values listed in that module. After that, head on over to Github and look for the filename that contains the code for the module you’re interested in. For example, the code for the
Listmodule is contained in
List.elm, and the code for
Maybeis contained in
Maybe.elm. Once you’re in the right file, search for the function, type, or value you’re looking for and settle in for some interesting reading.
Using Tuples To Pattern Match in Case Expression
One of the reasons the implementation of the
map2 function is so compact is because Elm allows us to use tuples to match complex patterns in a
case expression. If the
map2 function didn’t use a tuple in the
case expression above, here is what its implementation would look:
It’s a bit difficult to understand the code above due to too much nesting. In contrast, the implementation we saw earlier with tuples is much more elegant and easy to read. Here’s a more compelling case for using tuples in a
The code above is the implementation of
Result.map5 which takes a function and five results as arguments. Imagine what its implementation would look like if it didn’t use tuples to match patterns. Here’s an example of how to use
Add the above code right above the
main function in the
Playground.elm file located in
beginning-elm/elm-examples. After that you can type the function name in the repl to see the result.
Pattern Matching Can’t Do Computation
It’s important to realize that pattern matching can only look at the structure of data. It can’t do any computation on the data itself. In the Case Expression section, we tried to rewrite the following
if expression with
When we ran the
escapeEarthWithCase function we received the following error.
We received a syntax error because we tried to perform computations on
speed while matching patterns. Elm doesn’t allow that. If we do need to perform computations before matching patterns, we can do so inside a
Pattern Matching Lists
Pattern matching could be used to reach into values contained inside almost any data structure. Let’s explore how we can take advantage of pattern matching to simplify code that involve list manipulation. In the Easier Code Organization section, we saw how the
List module implements the
case expression uses
 to match an empty list. When pattern matching a list, you’ll often see
x being used to match a single element and
xs to match several. The reason behind that is,
x is a common variable name in mathematics and
xs is treated as the plural form of
x. Here’s a slightly more complex example of pattern matching in a list:
The code above is the implementation of the
foldl function we went through in the List section.
foldl extracts the first element from the given list, adds it to the accumulator, and applies itself recursively to the remaining items in the list. Eventually, the list runs out of items. When that happens it simply returns the accumulator. It then starts accumulating results from each invocation. Let’s use an example to further explore the above implementation.
foldl to compute the sum of all elements in a list. Like any other recursive function,
foldl exhibits three important characteristics:
#1 Reducing the problem:
foldl reduces the original problem to smaller sub-problems by taking the first element out from the given list.
This is the part we’re most interested in as far as pattern matching is concerned.
Behind the scenes, the
[ 1, 2, 3, 4 ] list is constructed like this:
That’s why we’re able to pattern match a list using the cons (
#2 Providing a base case:
foldl simplifies the problem with each invocation so that it will eventually reach the following base case.
#3 Combining results from each sub-problem: Once the base case is reached,
foldl starts the process of combining results from each invocation. It uses the following code to accomplish that.
It’s not easy to figure out how the accumulation of results happens just by looking at that code. So let’s visualize each step in the invocation.
Pattern Matching Function Arguments
We can pattern match on function arguments too. Here’s an example from the Tuples section that computes the perimeter of a triangle:
The pattern we are checking for in the above example is a tuple that contains three, and only three, numbers. When we apply the function, Elm reaches into the tuple that represents the sole argument of the
trianglePerimeter function and binds
6. Let’s say we want to ignore the second argument. Not sure why we’d want to do that in this case, but if we do, we could use
_ instead of an actual parameter.
Notice how the
trianglePerimeter function’s type got changed with the use of
_. This ability to pattern match function arguments came in handy when we used a
tuple fuzzer to write tests in the Fuzz Testing section.
tuple fuzzer generates two integers, it binds them to the constants -
num2 - contained inside the tuple parameter. That’s what enables us to use
num2 in the function body directly without having to use the
The implementation of the
List.unzip function is another good example of matching patterns on function arguments.
unzip function converts a list of tuples into a tuple of lists. Here’s an example:
The first list in the output contains the first item from each tuple in the original list, and the second list contains the second items. The private function
unzip’s implementation uses pattern matching to break the pairs apart and put them into separate lists. Look how succinct the code inside the
step function is. Without pattern matching it would take a lot more code to accomplish the same task.
Pattern Matching Records
In the Creating Our Own Types section, we created a simple function that checked a logged in flag to return an appropriate welcome message.
Let’s say we want to return a personalized welcome message that contains the user’s name. We can accomplish that by adding a second parameter. Add the following function definition right above
Instead of passing each user data individually, we can actually pass an entire user record to
welcomeMessage through pattern matching. Modify the
welcomeMessage function like this:
Next, add the following type alias right above
Now we can create a
User record using the constructor function and pass that to
What will happen if we pass a record that contains only name and logged in status?
That works! How about this:
That works too. Basically, the
welcomeMessage function works with any record that contains
isLoggedIn. This was made possible by applying pattern matching to the function argument.
The pattern matching version of
welcomeMessage has an interesting type annotation:
This is called extensible record syntax. It says the argument can be any record (represented by
a) as long as it has
isLoggedIn whose type is
name whose type is
String. The part that makes our argument an extensible record is this:
a |. If the type annotation were to be this instead:
We wouldn’t be able to pass any record that contains
name. It expects a record that has exactly two elements:
Although pattern matching is most evident in
case expressions, Elm allows us to use it in other places too. We looked at several examples of how to use tuples to simplify the logic in a function or a
case expression through pattern matching. We also saw an example that showed us how pattern matching on a list could enable us to write compact recursive functions such as
foldl. We learned what extensible records are and how pattern matching makes them flexible. Finally, it’s important to keep in mind that pattern matching can only look at the structure of data. It can’t do any computation on the data itself.