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 you like 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 in
src/List.elm, and the code for
src/Maybe.elm. Once you’re in the right file, search for the value you’re looking for and try to understand how it’s implemented.
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:
The code above isn’t easy to understand due to too much nesting. In contrast, the implementation with tuples we saw earlier is much easier to read. Unfortunately, Elm doesn’t allow more than three elements in a tuple. Otherwise, we would have been able to write code like this:
The code above is a fictitious implementation of
Result.map5 which takes a function and five results as arguments. Since we can’t put more than three elements in a tuple, the actual implementation of that function looks something like this:
It’s not as easy to read as the previous implementation. Here’s an example of how to use
map5, in case you are curious:
Add that code right above
main in the
Playground.elm file located in
beginning-elm/src. After that import the
Json.Decode module and expose
resultMap5Example in the module definition.
Now enter the following code in
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
mySpeed 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 it to simplify code that involve list manipulation. In the Creating a Module 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 a 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 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 Using Custom Types section, we created a simple function that checked the
isLoggedIn 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
welcomeMessage in the module definition.
Now enter the following code in
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
User in the module definition.
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 because 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.