/ miscellaneous elixir

Pattern matching

As computer science students/professionals, when we hear the term pattern matching, the first thing which comes to mind, is the tricky regular expressions which we write to match certain pieces of text(called a pattern), within a large text blog(called plain text). Well, this post is not about regular expressions at all.

In certain languages like erlang/elixir, the = operator has a polymorphic functionality. When somebody asks us about the function of =(called the equal to operator), the usual answer that comes out is that, its an operator which is used to bind a variable to a value. But this is not the case in erlang/elixir. Let’s see what’s so special about =, in these languages.

The = operator in elixir/erlang is like an assertion, which succeeds if the language can find a way of making the left-hand side equal to the right-hand side, and henceforth the = operator is called the match operator in these languages.

You might be like Whaat! don’t worry it did not make sense for me as well, when i read this for the very first time. Let’s see some examples

iex> a = 1
1
iex> 1 = a
1
iex> 2 = a
  1. In this case the variable a is unbound as of now, hence elixir was able to bind the value 1 to a and make the match succeed. After looking at this example you can argue that this is just an assignment what kind of match is being performed here ? To answer this, let’s look at the second example.
  2. Here well the left-hand-side of the = operator is a value 1, and we are asserting for the value of the variable a, since a was previously bound to 1, this match succeeds.
  3. In this case, left-hand-side of the = operator is a value 2, the right-hand-side is the variable a which is bound to value 1, the match cannot succeed because 2 never equals 1 hence we get a match error.

All these things take a while to understand because our brains are programmed to think of the = operator as an assignment operator, it will definitely take a while and a fair amount of effort to convince yourself to adapt to this new behaviour(because of our system1 and the lazy system2 inside of our brain. Source : Thinking fast and slow).

But this new construct makes manipulations with complex variables(such as tuples and lists) a lot easier. Less obviously it allows us programmers to write elegant, declarative-like conditionals and loops. Let’s go through some examples

Matching tuples A tuple is collection of elements in elixir, with constant access time to elements(analogous to arrays in C/Java).

iex(1)> {name, age} = {"Kumar D", 26}

The above expression assumes that the right hand side is a tuple of two elements. When the expression is evaulated, the variables name and age are bound to the corresponding elements of the tuple. We can now verify that these variables are correctly bound:

iex(2)> name
"Kumar D"
iex(3)> age
26

This feature is useful when you call a function which returns a tuple and you want to bind individual elements of that tuple to separate variables. The following example calls an erlang function :calendar.local_time/0 to get the current date and time

iex(4)> {date, time} = :calendar.local_time()

What happens if the right hand side doesn’t correspond to the pattern? The match fails, and a MatchError is raised

iex(7)> {name, age} = "can't match"
** (MatchError) no match of right hand side value: "can't match"

Matching constants Constants in erlang/elixir are called atoms, matching on constants are useful in compound matches. For example, it’s common to use tuples to group various fields of a record. The following snippet creates a tuple that holds a person’s name and age:

iex(2)> person = {:person, "Kumar D", 25}

The first element in the above tuple is a constant atom :person, which we can use to denote that this tuple represents a person. Later we can rely on this knowledge and retrieve individual attributes of the person

iex(3)> {:person, name, age} = person
{:person, "Kumar D", 25}

Here the runtime expects the right hand side term to be a three-element tuple, with its first element having a value of :person. After the match, the remaining elements of the tuple are bound to the variables name and age, let’s verify

iex(4)> name
"Kumar D"
iex(5)> age
25

Many functions in elixir/erlang return either {:ok, result} or {:error, reason. For example, let’s imagine that our system relies on a configuration file and expects it to always be available. We can read the file contents with the help of the File.read/1 function:

{:ok, contents} = File.read("my_app.config")

In this single line of code, there are three distinct things which are happening:

  1. An attempt to open and read the file my_app.config takes place.
  2. If the attempt succeeds, the file contents are extracted to the variable contents.
  3. If the attempt fails, an error is raised. This happens because the result of File.read/1, is a tuple in the form {:error, reason}, so the match to {:ok, contents} fails. Variables in patterns Whenever a variable name exists in the left-hand-side pattern, it always matches the corresponding right-hand-side term. In addition, the variable is bound to the term it matches(as we saw in the very first example). Occasionally we aren’t interested in a value from the right-hand-side term, but we still need to match on it. For example, let’s say we want to get the current time of the day. We can use the function :calendar.local_time/0, which returns a tuple {date, time}. But we aren’t interested in the date, so we don’t want to store it in a separate variable. In such cases, we use an anonymous variable(_):

    iex(1)> {_ time} = :calendar.local_time()
    iex(2)> time
    {20, 44, 18}

    when it comes to matching, the anonymous variable works just like a named variable: it matches any right-hand-side term. But the value of the term isn’t bound to any variable. In some cases we would want to match against the contents of the variable, For this purpose, the pin operator(^) is used.

    iex(1)> expected_name = "Kumar"
    "Kumar"
    iex(2)> {^expected_name, _} = { "Bob", 25}
    ** (MatchError) no match of right hand side value: {"Bob", 25}

    Using ^expected_name in patterns says that we expect the value of the variable expected_name to be in the appropriate position in the right hand side term. Matching lists List matching is very similar to tuples. The below example decomposes a three-element list:

    iex(1)> [first, second, third] = [1, 2, 3]
    [1, 2, 3]
    iex(2)> first
    1
    iex(3)> second
    2

    Lists in erlang/elixir are linked lists, and matching them is often done by exploiting their recursive nature. A non empty list can be expressed in the form [head | tail], we can use pattern matching to put each of these two elements in separate variables

    iex(3)> [head | tail] = [1, 2, 3]
    [1, 2, 3]
    iex(4)> head
    1
    iex(5)> tail
    [2, 3]

Matching with functions The above pattern-matching mechanism can also be used in the specification of function arguments. A basic function definition is of the form

def my_func(arg1, arg2) do
    ...
end

The argument specifiers arg1 and arg2 are patterns, and we can use standard pattern matching techniques here. Let’s see an example: We saw that tuples are used to group related fields together. If we have a geometry application, we can represent a rectangle with a tuple, {a, b}, containing the rectangle’s sides. With this convention we can have a function which calculates area like below

defmodule Rectangle do
    def area({a, b}) do
        a * b
    end
end

Here, we pattern match on the argument provided to the area function, we expect it to be a two-element tuple. We then binds the corresponding tuple elements into variables and then return the result. Let’s see this in action

iex(1)> Rectangle.area({2, 3})
6
iex(2)> Rectangle.area(2)
** (FunctionClauseError) no function clause matching in Rectangle.area/1
iex:2: Rectangle.area(2)
  • In the first case we are passing the {2, 3} two-element tuple and the match succeeds subsequently the area gets computed.
  • In the second case, we pass 2 which a value, hence the match fails returning FunctionClauseError.

Multi clause functions

In erlang/elixir, we can overload a function by specifying multiple clauses. A clause is a function definition specified by the def construct. If we provide multiple function definitions of the same function with the same arity, it’s said that the function has multiple clauses. Let’s see this in action. Extending the previous example, let’s say you need to develop a Geometry module that can handle various shapes. You’ll represent shapes with tuples and use the first element of each tuple to indicate which shape it represents like:

rectangle = {:rectangle, 4, 5}
square = {:square, 5}
circle = {:circle, 4}

Given the above shape representations, we can write a function to calculate a shape’s area as follows

defmodule Geometry do
    @pi 3.1412

    def area({:rectangle, a, b}) do
        a * b
    end
    
    def area({:square, a}) do
         a *a
    end

    def area({:circle, r}) do
        r * r * @pi
    end

The above example provides three clauses of the same function. Depending on which argument we pass, the appropriate clause is called.

Conclusion

The above examples shows how a simple and declarative syntax of the language helps in writing clear and concise programs with very good readability.

Kumar D

Kumar D

Software Developer. Tech Enthusiast. Loves coding 💻 and music 🎼.

Read More
Pattern matching
Share this