Playing With Fire

Exploring the web one Elixir at a time

Functions, Functions and more Functions

Elixir is a functional programming language, and as such you should expect that it has functions. In fact, like many functional and imperative languages it has two sorts: anonymous and named.

Before I go on and discuss functions, I need to cover the concept of arity. The arity of a function is the number of arguments that it takes. This is important in Elixir (and Erlang) because functions with the same name, but different aritys are different functions and have a different type.

This means that:

func(a, b)           # func/2
func(a, b, c)        # func/3
func(a, b, c, d)     # func/4

are all different.

As you can see the arity of a function is denoted by /x after the function name. The arity of the funciton helps Elixir use the right thing.

Anyway back to the billed discussion.

 

Anonymous Functions

As you might have expected for a functional programming language, functions are first-class citizens and anonymous functions are no exception.

In Elixir the syntax for anonymous functions is as follows:

fn
  parameter_list -> function_body
  parameter_list -> function_body
end

because of pattern-matching, the function can take more than one set of parameter lists to provide alternate bodies.

iex(1)> checker = fn
...(1)> {:ok, a} -> a
...(1)> {:error, a} -> "error"
...(1)> end
#Function<6.90072148/1 in :erl_eval.expr/5>
iex(2)> checker.({:ok, 1})
1
iex(3)> checker.({:error, 1})
"error"

As you can see to call an anonymous function dot-notation is used. From the example as well you can see that the function body executed is determined by the pattern of the parameter list. This is a useful concept to understand when we move to move advanced topics.

After the function definition iex prints out the internal reference to that function.

As functions are first-class citizens in Elixir, functions are a valid return type for functions, meaning that functions can return functions (that can return functions ad infinitum). This is demonstrated by the following rather contrived example:

iex(1)> fun = fn -> (fn name -> "Hello #{name}" end ) end
#Function<20.90072148/0 in :erl_eval.expr/5>
iex(2)> hello = fun.()
#Function<6.90072148/1 in :erl_eval.expr/5>
iex(3)> hello.("World")
"Hello World"

This syntax for anonymous functions is all well and good, but it can look a little bit verbose when required for quick data transformations or small helper functions in operations like Enum.map/2.

Luckily Elixir provides us with some syntactic sugar to make these things more readable. &():

iex(1)> Enum.map([1,2,3,4,5,6], &(&1 * &1))
[1, 4, 9, 16, 25, 36]

which is equivalent to:

iex(2)>> Enum.map([1,2,3,4,5,6], fn a -> a * a end)     
[1, 4, 9, 16, 25, 36]

It is of course a matter of opinion, but for me the first shorter form is more readable.

In this alternative syntax, &n is an parameter list index, so &1 means the first parameter, &2 the second and so on:

iex(3)> add = &(&1 + &2)
&:erlang.+/2
iex(4)> add.(1,2)
3
...
iex(6)> subtract = &(&1 - &2)
&:erlang.-/2
iex(7)> subtract.(2,3)
-1
iex(8)> subtract = &(&2 - &1)
#Function<12.90072148/2 in :erl_eval.expr/5>
iex(9)> subtract.(2,3)       
1

 

Named Functions

A named function is just that: a function with a name. In Elixir named functions reside within modules and a module lives in a file.

There are two types of file, script file (with .exs extension) and code file (with .ex extension). The difference is that files with the .ex extension are intended for compilation with elixirc, whilst .exs are intended for use with elixir directly.

To demonstrate this, put this file in a location of your choosing on your file system: MyMath.ex In this file put the following:

defmodule MyMath do
    def plus(a, b) do
        a + b
    end

    def multiply(a, b) do
        a * b
    end

    def minus(a, b) do
        a - b
    end

    def divide(_, b) when b == 0 do
        raise "Cannot divide by zero"
    end

    def divide(a, b) do
        a / b
    end
end

As you can see this is just a simple set of basic math operations, nothing too complicated about that.

What you might also notice is that divide is defined twice, once for the case where the denominator is zero and then for all other cases. This is a typical pattern, which makes use of pattern-matching.

To use this, from the directory where you put this file, start an iex session:

$ iex
Erlang/OTP 17 [erts-6.2] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.1.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

The first thing that we’ll do here is use the iex c() function to compile and include this module.

iex(1)> c("MyMath.ex")
[MyMath]
iex(2)> 

we can then start using the functions contained within this module in iex:

iex(2)> MyMath.plus(1, 2)
3
iex(3)> MyMath.multiply(3, 9)
27
iex(4)> MyMath.minus(200, 300)
-100
iex(5)> MyMath.divide(3, 4)
0.75
iex(6)> MyMath.divide(300, 0)
** (RuntimeError) Cannot divide by zero
    MyMath.ex:15: MyMath.divide/2

so here you can see each of the operations defined in the file being used. As you can see, to be able to call the function you need to call it using the module name first. This namespacing is necessary when calling a function in this manner. If you are calling a function from within the same module then it is not necessary to use the module name, as demonstrated in the following listing for MyMath2.ex:

defmodule MyMath2 do
    def plus(a, b), do: operation(a, b, &+/2)

    def minus(a, b), do: operation(a, b, &-/2)

    def multiply(a, b), do: operation(a, b, &*/2)

    def divide(_, 0), do: raise "Divide by zero error"

    def divide(a, b), do: operation(a, b, &//2)

    defp operation(a, b, func), do: func.(a, b)
end

I’m also demonstrating here two other features that Elixir has: alternative function definition syntax and the capture operator (&).

So to recap Elixir named function definition:

defmodule ModuleName do
    def function(arg1, arg2), do: functionBody

    def function(arg1, arg2), do: (
        functionBody
    )

    def function(arg1, arg2) do
        functionBody
    end
end

For a typical example of recursion - factorial.ex:

defmodule Factorial do
    def of(0), do: 1
    def of(n), do n * of(n-1)
end