Subscribe to access all episodes. View plans →

#10: Intro to Elixir Functions

Published March 26, 2017

Elixir 1.4.2

Source code on GitHub


Elixir is a functional programming language and at its heart are, of course, functions.

In this episode we’ll see how to create functions and see what they can do.

So let’s start by defining some functions.

Now in Elixir functions are organized in to modules.

Let’s create a module to help us do some simple calculations.

We’ll name it Calculator.

calculator.ex

defmodule Calculator do

  def squared(x) do
    x * x
  end

  def product_of(x, y) do
    x * y
  end

  def sum_of(x, y) do
    x + y
  end

end

I’ve added these functions off-camera, but let’s walk through them.

Our first function ‘squared’ takes a variable, x, and multiplies it by itself.

‘product_of’ multiplies the two variables, x and y, returning their value.

And ‘sum_of’ adds two variables, x and y, and returns their value.

Now that we have some functions defined inside a module, how can we test them out?

Elixir comes with a Read-Eval-Print-Loop or REPL called ‘IEx’. Let’s start up ‘IEx’ and call the module we want to be included, in this case ‘calculator.ex’.

$ iex calculator.ex

In order to call a function in Elixir we want to use the module name, in this case ‘Calculator’, and then the function we want to invoke, we’ll use squared here. And pass in any arguments the function expects.

> Calculator.squared(4)
16
> Calculator.product_of(4, 2)
8
> Calculator.sum_of(1, 1)
2

Great all the functions from our Calculator module returned the values we expected.

Now let’s add another function to our module.

Back in our module we’ll add a function named subtract, that simply subtracts y from x.

calculator.ex

defmodule Calculator do

  def subtract(x, y) do
    x - y
  end
  …
end

With our function saved, we’ll go back to ‘IEx’.

And instead of restarting our ‘IEx’ session we can reload our ‘Calculator’ module with ‘r(Calculator)’.

> r(Calculator)
warning: redefining module Calculator (current version defined in memory)
  calculator.ex:1

{:reloaded, Calculator, [Calculator]}

Now let’s call our subtract function:

> Calculator.subtract(4, 2)
2

Elixir also supports private functions, which can only be accessed from he current module.

Let’s go back to our ‘Calculator’ module and update our subtract function to have a private function called ‘do_subtract’ handle the work.

In order to define a private function, we’ll use ‘defp’ and then the name of the function.

calculator.ex

defmodule Calculator do

  def subtract(x, y) do
    do_subtract(x, y)
  end
  defp do_subtract(x, y) do
    x - y
  end
  …
end

Functions in Elixir also support another great feature called guard clauses. Let’s add a guard clause to our subtract function that prevents a negative number as a result.

To do that we’ll add when x is greater than or equal to y. When this clause is true, our ‘do_subtract’ private function will be called.

No we need to add another function to handle cases where y is greater than x.

And in those cases we’ll just return a message.

calculator.ex

defmodule Calculator do

  def subtract(x, y) when x >= y do
    do_subtract(x, y)
  end
  def subtract(x, y) when x < y do
    "x must be greater than y"
  end
  …
end

Let’s go back to ‘IEx’ and take our updated subtract function for a spin.

> r(Calculator)
warning: redefining module Calculator (current version defined in memory)
  calculator.ex:1

{:reloaded, Calculator, [Calculator]}
> Calculator.subtract(5, 2)
3
> Calculator.subtract(2, 5)
"x must be greater than y"

A benefit of using Elixir is that since it’s built on the Erlang VM we can call Erlang functions from our Elixir code.

Now one common use case you’ll see if we need to ‘sleep’ for a period of time in our program, we’d need to use Erlang’s Timer module.

So let’s see how we could do this.

To call an Erlang module in Elixir we’ll use a lowercase atom. So for ‘timer’, we’d use the atom ‘:timer’ and then call the sleep function, passing in how long we want to sleep, in this case 1 second:

calculator.ex

defmodule Calculator do

  def subtract(x, y) when x >= y do
    :timer.sleep(1_000)  
    do_subtract(x, y)
  end
  …
end

Another way we can use the ‘:timer’ module is to alias it.

calculator.ex

defmodule Calculator do
  alias :timer, as: Timer

  def subtract(x, y) when x >= y do
    Timer.sleep(1_000)
    do_subtract(x, y)
  end
  …
end

We’ll go back to ‘IEx’, reload our module, and if we call the subtract function, we see it sleeps for 1 second before performing our calculation.

Elixir also supports another type of function - anonymous functions.

Let’s create an anonymous function that does the same thing as our squared function.

We’ll store our function in a variable named ‘squared’ .

> squared = fn(x) -> x * x end
#Function<6.52032458/1 in :erl_eval.expr/5>

We can use Elixir’s Kernel function ‘is_function’ to check if our variable is indeed a function. And great it looks like it is.

> is_function(squared) 
true

So let’s call it.

Anonymous functions are called with a dot between the variable and the parenthesis.

> squared.(4)
16

Because anonymous functions are so common in Elixir, there’s a nice shorthand to write them as well.

We’ll use the the ampersand or “capture” operator, to create it. Ampersand one represents the first argument passed into our function. You need multiple arguments, they would be represented as ampersand 2, 3, and so on.

> squared = &(&1 * &1)
> squared.(5)
25

We’ll call our new squared function in the same way with a dot. And great it returned 25.

The ‘capture’ operator also allows us to grab a named function from a module.

Let’s test this out by capturing our subtract function in a variable named ‘subtractor’.

We’ll wrap Calculator.subtract/2 in an ampersand

> subtractor = &(Calculator.subtract/2)
&Calculator.subtract/2

The ‘/2’ refers to a functions arity or how many arguments a function accepts - in this case it’s 2.

Now our subtract function is ‘captured’ in the ‘subtractor’ variable. Let’s call it:

> subtractor.(5, 1)
4
> subtractor.(1, 5)
"x must be greater than y"

Another trick when defining anonymous functions is we can also return different data types.

To return a list, we’ll use square brackets in place of parentheses.

> my_list = &[0, &1, &2]
#Function<12.52032458/2 in :erl_eval.expr/5>
> my_list.(1, 2) 
[0, 1, 2]

And to return a tuple we’ll use curly brackets.

> my_tuple = &{:ok, &1}
#Function<6.52032458/1 in :erl_eval.expr/5>
> my_tuple.("hello!")
{:ok, "hello!"}

© 2024 HEXMONSTER LLC