Subscribe for only $15 to access all of our content

#34: Elixir Guards

Elixir 1.6


Sign up for our newsletter to get notified when new episodes drop

In this episode let’s look at how to use guards in Elixir.

While guards aren’t limited to only function clauses, that’s what we’ll be focusing on in this episode.

Using guards in our function clause will allow us to set the rules for when a function is executed based on the arguments passed in.

To get started let’s look at the Elixir docs to see what expressions we can use to build guards.

We can combine any of these expressions to build a guard clause.

For our example we have a Shirt module and inside we have three different lists containing different colors available for small, medium”, and large sized shirts.

Let’s create function named colors_for that will take one argument - the size of a shirt - and return a list with the available colors for that size.

The sizes we’ll support are small, medium, and large.

Let’s start by implementing colors_for for small.

We’ll take the size and use the when keyword to start our guard.

In most cases the size will be the integer representation. 1 represents a “small” shirt size. So let’s include that condition.

A shirt size can also be string of a lowercase "s" or capitalized "S".

So let’s update our guard to account for the lowercase "s"

Or the capitalized "S".

Great now our colors_for function will trigger for our small sizes.

Let’s now create another color_for function for medium sizes.

It will support our integer representation for medium - 2. As well as a lowercase "m" or a capitalized "M"

Then let’ create our colors_for function for large sizes.

Our integer representation for them is 3. As well as a lowercase "l" and a capitalized "L".

shirt.exs

defmodule Shirt do
  def colors_for(size) when size == 1 or size == "s" or size == "S" do
    ["Green", "Blue", "Pink"]
  end
  
  def colors_for(size) when size == 2 or size == "m" or size == "M" do
    ["Red", "Blue"]
  end
  
  def colors_for(size) when size == 3 or size == "l" or size == "L" do
    ["Red", "Black", "Blue", "Yellow"]
  end
end

Let’s test it out. We’ll go to the command line and start iex with our module.

$ iex shirt.exs
> Shirt.colors_for(1)
["Green", "Blue", "Pink"]
> Shirt.colors_for("s")
["Green", "Blue", "Pink"]
> Shirt.colors_for("M")
["Red", "Blue"]
> Shirt.colors_for("l")
["Red", "Black", "Blue", "Yellow"]

Great, our sizes are returned as expected.

Now that we’re successfully using guard clauses to determine which colors_for function to trigger, let’s imagine we needed to use this same guard in many different modules of our application.

While we could copy the same code we have here, there’s a better way.

As of Elixir 1.6 we can create custom guards.

Let’s take our guards here and turn them into custom guards that we can name and easily use in different modules.

We’ll create a new module to house our guards named SizeTranslator that we’ll define above our Shirt module.

Let’s open the docs to see how we can create a guard.

We see that we use defguard to create our guard.

We’ll give it a name, followed by the when keyword and then our conditions.

Let’s go back to our module.

And create our first custom guard called is_small passing in our our size

Then we’ll copy the conditions from below.

Let’s do the same for when a shirt is medium

And for when it’s large.

shirt.exs

defmodule SizeTranslator do

  defguard is_small(size) when size == 1 or size == "s" or size == "S"

  defguard is_medium(size) when size == 2 or size == "m" or size == "M"

  defguard is_large(size) when size == 3 or size == "l" or size == "L"

end

defmodule Shirt do
  import SizeTranslator

  def colors_for(size) when is_small(size) do
    ["Green", "Blue", "Pink"]
  end

  def colors_for(size) when is_medium(size) do
    ["Red", "Blue"]
  end

  def colors_for(size) when is_large(size) do
    ["Red", "Black", "Blue", "Yellow"]
  end

end

Then we can update our Shirt module to use our new guards.

We’ll update each function to use it’s respective guard we wrote.

Since we’re now using the guards in this module we’ll need to import them, so let’s import SizeTranslator.

Then let’s go back to our iex session and reload our module.

And let’s try our functions again.

> r(Shirt)
{:reloaded, Shirt, [SizeTranslator, Shirt]}
> Shirt.colors_for(1)
["Green", "Blue", "Pink"]
> Shirt.colors_for(2)
["Red", "Blue"]
> Shirt.colors_for(3)
["Red", "Black", "Blue", "Yellow"]
> Shirt.colors_for("S")
["Green", "Blue", "Pink"]

And great our new custom guards are working and we’re seeing the expected results.