Elixir School
Presents

# Clean Control Flow in Elixir with Pattern Matching and Immutability

Learn how to use pattern matching instead of guard clauses to implement really clean control flow in Elixir.

One of the features that fascinate me most about Elixir is pattern matching. I always wonder if there is a way to solve what I need using it and I love exploring it. When you combine the beauty of pattern matching with the power of immutability some things almost seem magical but they are not! It is not my focus to cover everything about pattern matching and immutability but instead to demonstrate how we can use pattern matching instead of guard clauses to implement clean control flows in Elixir.

For this post we’ll focus on implementing logic for the tabletop game Battleship. The first rule we’ll implement is simple: a player cannot go twice in a row. One way to solve this is to track the last player that performed a move. With this information we now have two possibilities: If the player who is trying to make a move is the same as the last player who take action we will just ignore the move. Otherwise we can will compute the move.

Depending on our experience with Elixir we might reach for a conditional as the first solution, something like:

``````def maybe_move(player, last_player) do
if player != last_player do
player
|> make_a_move()
|> set_as_last_player()
else
:ignored
end
end``````

Or even pattern matching with guard clause

``````def maybe_move(player, last_player) when player == last_player do
:ignored
end

def maybe_move(player, last_player) do
player
|> make_a_move()
|> set_as_last_player()
end``````

But it is possible to combine the pattern matching we already used in the guard clause solution with the power of immutability to come up with an even more alchemistic solution!

``````def maybe_move(last_player, last_player) do
:ignored
end

def maybe_move(player, last_player) do
player
|> make_a_move()
|> set_as_last_player()
end``````

Wait a second, what have we done here?

We define the first version of the `maybe_move` function to take in a first and second argument named `last_player`. This means the function will only match if the player provided as a first argument matches the player provided as a second argument Thanks to immutability, when we call both arguments by the same name Elixir will check if they are actually the same! We could easily call both arguments player or even something like player_is_the_last_player. It doesn’t matter! The rule is just that if we want to ensure that there is equality, we call both arguments by the same name!

Ok, it’s time to play using our nice little code!

Let’s say we have `player1` and `player2`, `player1` made the most recent move and therefore is our last_player and now `player2` will try to move!

So we will call the function `maybe_move(player2, player1)` where `player2` is the player who wants to make a move and `player1` is our last_player

We have two `maybe_move` functions both with arity 2, so Elixir will try to pattern match from top to bottom, i.e. the first function it will try to match will be

``````def maybe_move(last_player, last_player) do
:ignored
end``````

Our first argument is `player2` and Elixir will bind it with `last_player = player2` and since our second argument is also a `last_player`, Elixir will use the `^` (pin operator) to check if the previous bind is valid for the second argument instead of trying to rebind it.

``````last_player = player2
ˆlast_player = player1``````

As player2 is different from player1, we will not have a valid pattern match and therefore Elixir will move on to try to match with the next function!

Our next match attempt!

``````def maybe_move(player, last_player) do
player
|> make_a_move()
|> set_as_last_player()
end``````

Now the behavior will be different, we are asking Elixir to match two different arguments. That is, to make a bind for each one.

``````player = player2
last_player = player1``````

With a valid match, our function will run! Player2 will make a move and then will be registered as our new `last_player`!

What if player2 tries another move in a row? Well, we will call our first `maybe_move` function again and try a match. Player2 wants to make a move and is also `last_player`, so we get the following call:

`maybe_move(player2, player2)`

Trying to match it with the first maybe_move function we get the following match:

``````def maybe_move(last_player, last_player) do
:ignored
end``````
``````last_player = player2
ˆlast_player = player2``````

Which is a valid match! And since our function just ignores the move attempt, nothing will happen until another player attempts a move! That’s it! We’ve learned how pattern matching and data immutability together can provide an elegant solution to control flows, another tool in our Elixir toolbox!