The Beauty of Pattern Matching in Elixir


Learn Elixir and Phoenix

Join happy developers - learn Elixir and Phoenix to build cool features and apps!

Subscribe to receive FREE tutorials and tips.

     

    Absolutely no spam, ever. I respect your email privacy. Unsubscribe at any time.

    Transcript

    One of the reasons I fell in love with Elixir is pattern matching and how it can be extensively used all over the code.

    Match Operator

    Let’s start the interactive Elixir console, iex and let’s do what is seems to be a normal assignment

    x = 1
    

    In this way we bind the variable x to the value 1, using the match operator =, which tries to match the right part to the left part.

    1 = x
    1 = 1
    

    These are also a valid expressions, since the two sides match.

    iex> 1 = 2
    ** (MatchError) no match of right hand side value: 2
    
    iex> 2 = x
    ** (MatchError) no match of right hand side value: 1

    Since the two sides don’t match we get a MatchError.

    We can re-bind the variable with a new value

    iex> x = 2
    2
    iex> 2 = x
    2

    Tuples

    Let’s consider a tuple of three elements {1, 2, 3}. We want to bind a variable to each element.

    iex> {x, y, z} = {1, 2, 3}
    iex> x
    1
    iex> y
    2
    iex> z
    3
    

    The two sides match and each number of the tuple on the right is assigned to a variable on the left.

    If we try to match two different tuples, with different number of elements, they obviously don’t match, triggering a MatchError.

    iex> {x, y, z} = {1, 2, 3, 4}
    ** (MatchError) no match of right hand side value: {1, 2, 3, 4}
    

    We can also specify one value, in the left tuple, along with the variables.

    iex> {1, y, z} = {1, 2, 3}
    

    In this way we force the first element of the tuple, which needs to match.

    iex> {2, y, z} = {1, 2, 3}
    ** (MatchError) no match of right hand side value: {1, 2, 3}
    

    In this case the first element of the two tuples is not the same, so the two sides don’t match.

    ^ pin operator

    When a variable is bound to a value, and we don’t want to rebind it during a match expression we can use the ^ pin operator

    iex> x = 1
    iex> {^x, y, z} = {1, 20, 30}
    

    In this way, instead of re-binding the variable x to the value 1, we are forcing a match against its current value.

    iex> {^x, y, z} = {10, 20, 30}
    ** (MatchError) no match of right hand side value: {10, 20, 30}
    

    We see how the last expression leads to a MatchError since is like doing

    iex> {1, y, z} = {10, 20, 30}
    

    :ok, :error

    This is useful when we want to check if a function returns successfully

    iex> {:ok, file} = File.open "hello.txt", [:write]
    {:ok, #PID<0.105.0>}
    

    The File.open function returns successfully with a {:ok, ...} tuple, so we match the :ok atom and assign the opened file to the file variable.

    When we try to make the File.open to fail, it returns an {:error, error} tuple

    iex> {:ok, file} = File.open("/invalid/directory/hello.txt", [:write])
    ** (MatchError) no match of right hand side value: {:error, :enoent}
    

    and it doesn’t match the {:ok, file} tuple on the left.

    Control Flow

    With pattern matching we can be really explicit about the cases we want to handle.

    case File.open("hello.txt", [:write]) do
      {:ok, file} ->
        IO.write(file,"hello world")
        File.close(file)
    
      {:error, error} ->  
        IO.puts("Error openings the file: #{inspect error}")
    end
    

    We saw previously that the File.open function returns a tuple of two elements.

    • If it opens the file successfully, it returns {:ok, file} and we then close the file after writing a string into it
    • if there was an error opening the file, it returns {:error, error} and we just print the error code

    When we ignore a part of the match, and we don’t want to assign that part to any variable, we use the underscore: _

    iex> {1, 2, _} = {1, 2, 3}
    {1, 2, 3}
    iex> {1, 2, _} = {1, 2, 4}
    {1, 2, 4}

    With _ the matching value is ignored and not assigned to any variable

    Lists

    We can also pattern match other data structures like Lists. We can do what we did with the tuples but this time with lists

    iex> [1, b, 3] = [1, 2, 3]
    [1, 2, 3]
    iex> b
    2
    

    In this case we bind the variable b to the value 2.

    The cool thing is that with lists we can use pattern matching to split the head from the tail.

    iex> [ head | tail ] = [1, 2, 3]
    
    iex> head
    1
    iex> tail
    [2, 3]
    

    This is pretty useful when we want to go through the whole list of elements recursively.

    defmodule Example do
      def next_element([ head | tail ]) do
        IO.puts("#{inspect(head)} : #{inspect(tail)}"
        next_element(tail)
      end
    
      def next_element([]) do
        IO.puts("finished")
      end
    end
    

    This only argument of the next_element function is a list.

    • The first clause of the function, matches when the list is not empty. It extract the head, prints the head and tail and recursively calls itself passing just the remaining part of the list, the tail.
    • next_element([]) matches when the list is empty, and we reached the end of the recursion.
    iex> Example.next_element [1, 2, 3, 4, 5]
    1 : [2, 3, 4, 5]
    2 : [3, 4, 5]
    3 : [4, 5]
    4 : [5]
    5 : []
    finished
    :ok
    

    Maps

    We can obviously use pattern matching also with Maps.

    eth_data = %{ "ETH" => 125.0 }
    btc_data = %{ "BTC" => 3575.0 }
    

    In this case the two maps represent two HTTP responses from an API. We want now to handle these two cases in two different ways.

    We can use pattern matching and multi-clauses function to process both the responses.

    defmodule API do
    
      def process(%{"BTC" => btc}) do
        IO.puts("Bitcoin: #{btc}")
      end
    
      def process(%{"ETH" => eth}) do
        IO.puts("Ethereum: #{eth}")
      end
    
      def process(%{"LTC" => ltc}) do
        IO.puts("Litecoin: #{ltc}")
      end
    
      def process(_) do
        IO.puts("any other response")
      end
    end
    

    We see that using pattern matching it’s really easy and clean to define different clauses of the process function where we handle the different maps in different ways.

    The last function is then a catchall function that catches all the other cases.

    iex> API.process %{"ETH" => 125.0}
    Ethereum: 125.0
    iex> API.process %{"BTC" => 3575.0}
    Bitcoin: 3575.0
    
    iex> API.process %{"invalid key" => "hello"}
    any other response
    

    Wrapping up

    The are many other ways of using pattern matching in Elixir. This was just an intro and we will see pattern matching with binaries and structs in next episodes.

    Leave a comment below to tell me how you find pattern matching in Elixir and to tell me if you started to you pattern matching in your code.

    If you have any question please leave a comment in the comments section below. Subscribe to receive updates on new articles and episodes.

    Learn Elixir and Phoenix

    Join happy developers - learn Elixir and Phoenix to build cool features and apps!

    Subscribe to receive FREE tutorials and tips.

       

      Absolutely no spam, ever. I respect your email privacy. Unsubscribe at any time.