Elixir Pattern Matching Finale


The previous post Basics of Pattern Matching in Elixir describes the fundamentals of pattern matching in Elixir.

In this post, there is not much new to learn conceptually, but you will see how far and wide those fundamentals can take you. In general, pattern-matching in Elixir can does one or more of the following things:

  1. Matching value e.g. in the expression a = 1
  2. Matching types e.g. in the expression %Student{name: name} = %Student{name: "Alberto"}, the variable name will get bound to value "Alberto" for the right hand side struct of type Student.
  3. Matching shape e.g. in the expression {name, age} = {"Alberto", 12} value is assigned only because the ‘shape’ matches on both sides.

You don’t need to memorize the above three points. Instead, let’s take a look at some common patterns right away.

Pattern matching in case blocks

This is one of the most common use cases. Whereas in a cond block, you rely on conditions, in case blocks, you rely purely on pattern matching. Look how neatly you can handle an HTTP request.

url = "https://example.com"

case HTTPoison.get(url) do
  {:ok, %{status_code: 200, body: body}} ->
    Poison.decode!(body)

  {:ok, %{status_code: 404}} ->
    # handle 404

  {:error, %{reason: reason}} ->
    # handle error
end

Pattern Matching within Function Signature

A very clean function-overloading can be achieved in Elixir when dealing with Maps (or structs). Take a look at the following code-snippet.

def greet(%{lang: "he"}), do: "Shalom!"
def greet(_profile), do: "Hello!"

greet(%{name: "David", lang: "he"}) # returns "Shalom!"
greet(%{name: "David") # returns "Hello!"

Destructuring/ assignment within the function signature

Let’s take the function greet above for a spin. Why not also collect the name within the function signature itself?

def greet(%{lang: "he", name: name}), do: "Shalom! " <> name
def greet(_profile), do: "Hello!" <> name

greet(%{name: "David", lang: "he"}) # returns "Shalom! David"
greet(%{name: "David"}) # returns "Hello! David"

Of course, slightly different (and limited) versions of the above can be achieved with different data types as well.

Enforcing ’types’ with structs for function calls

You can define your own datatypes using structs in Elixir. And even though Elixir is a dynamically-typed language, the mechanism for function overloading is still there. You might have noticed how we could redefine greet above, and Elixir runtime would give us the versions of greet we are looking for.

Function overloading becomes a bit more interesting and snappier, when we use pattern-matching against structs in function signatures.

defmodule Cat do
  defstruct [:name]
end

defmodule Dog do
  defstruct [:name]
end

defmodule Animal do
  def makesound(%Cat{name: name}) do
    "Cat " <> name <> " says Meew!"
  end
  def makesound(%Dog{name: name}) do
    "Doggo " <> name <> " says Woof!"
  end
end

Now, if you ran,

cat = %Cat{name: "Garfield"}
dog = %Dog{name: "Tito"}
IO.puts(makesound(cat))
IO.puts(makesound(dog))

You will get the following outputs:

>> Cat Garfield says Meew!
>> Doggo Tito says Woof!

That was all I could come up with trying to show off 99% of the pattern-matching I use and encounter on a regular basis. Everything else you would encounter would be a remix of this and the previous post Basics of Pattern Matching in Elixir.