cult3

Functions as First-Class Citizens in Elixir

May 09, 2016

Table of contents:

  1. Assigning a function to a variable
  2. Passing functions as arguments to other functions
  3. The & operator
  4. Using Closures
  5. Conclusion

In Elixir, functions are first class citizens. This allows you to define a function and assign it to a variable, and then use that variable to invoke the function.

You can also pass a function as the argument to another function. This is often referred to as anonymous functions or lambdas in other programming languages.

Lambdas that reference a variable from outside of it’s scope are know as Closures.

In today’s tutorial we’re going to be looking at functions as first-class citizens in Elixir, how to assign a function to a variable, and how to pass functions as the arguments to other functions, and how to use Closures in Elixir.

Assigning a function to a variable

To assign a function to a variable, you would use the following syntax:

double = fn x -> x * 2 end

This defines a function that accepts a single argument. The argument is multiplied by 2 and then returned. An important thing to note is, it is the function definition itself that is bound to the double variable, not the return value of the function.

When a function bound to a variable, you can then call the function like this:

double.(2)

Anonymous functions are called using dot syntax to differentiate them from regular function calls. When you see a function being called using dot syntax, you will know its an anonymous function, rather than trying to find the regular function definition.

Passing functions as arguments to other functions

The ability to store a function definition as a variable is really useful. One such use case is the ability to pass a stored function definition to another function.

A lot of different programming languages have this concept of passing a function as an argument to another function. For example in Ruby, you can pass a function to the map method on an Object that implements the Enumerable mixin.

(1..4).map { |i| i * i }
# => [1, 4, 9, 16]

This will iterate over each value and pass it to the given function. The map method will then return a new array of the return values from the function.

This is the exact same principle that is going on in Elixir. We could rewrite the code above to Elixir like this:

Enum.map(1..4, fn i -> i * i end)
# [1, 4, 9, 16]

As you can see, the Elixir version is pretty much the same as the Ruby version. In this example, I’m passing a Range as the first argument, and an anonymous function as the second argument.

You can think of a Range as just a shorter way of defining a List:

Enum.to_list(1..4)
# [1, 2, 3, 4]

The second argument is the anonymous function, but when you are passing a function to another function as an argument, you don’t need to store it as a variable first.

So as with the Ruby version. The map function on the Enum module will iterate through each value of the list and pass it to the function.

The return value from the map function is a new List containing the return values from the anonymous function. In this case, by multiplying each value by itself.

The & operator

When reading Elixir code, you will often see the & operator used in anonymous functions.

For example, the following Elixir code:

Enum.each(1..3, fn x -> IO.puts(x) end)

Can be shortened to:

Enum.each(1..3, &IO.puts/1)

This is known as the capture operator. Under the hood, Elixir is taking the module name, function, and arity and converting it into a lambda for you.

You can also shorten the lambda definition from earlier using the capture operator:

Enum.map(1..4, &(&1 * &1))

Again, Elixir will automatically convert the second argument of the map function to a lambda.

Inside of the lambda, you can refer to each argument using &n where n is the nth argument of the function.

Using Closures

A lambda that references a variable outside of it’s scope is known as a closure.

name = "Philip"
# "Philip"

greet = fn -> IO.puts(name) end

greet.()
# "Philip"

In this example I’ve created a name variable and bound a value to it.

Next I define a greet lambda that prints the name variable to the screen. Notice how the name variable is not defined inside the scope of the function.

Finally I call the greet lambda and it prints the name to the screen.

As long as you hold a reference to the greet lambda, the name variable will also be available.

For example, you can rebind the name variable, and then call the greet lambda again, but the return value won’t change:

name = "Sheila"
# "Sheila"

greet.()
# "Philip"

By holding a reference to the lambda, you will continue to hold a reference to any variables it uses, even if those variables are changed.

Conclusion

Functions as first-class citizens can be confusing at first, especially if you aren’t already familiar with a programming language that has similar concepts.

Fortunately, once you are familiar with the concept, it carries pretty well between languages.

Defining anonymous functions is a really useful thing. For example, passing a lambda function to a function such as map allows map to be generic, whilst giving the developer the flexibility to define her own logic.

This is incredibly useful as you will find that you use functions like map, each and reduce a lot in your day-to-day programming!

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.