Playing With Fire

Exploring the web one Elixir at a time

The Capture Operator

The capture operator works a little bit like capture groups in regular expressions, but for function arguments. Use of this allows for shortcutting and making more readable short anonymous functions.

 

Helper and anonymous functions

Because the use of small helper functions is so ubiquitous in funcitonal programming and in Elixir especially, there is some syntactic sugar that is provided in order to make this type of thing much easier to do.

We have used this form in some examples before as demostrated in Functions, Functions and more Functions, but here is a quick recap

iex(1)> Enum.map([1,2,3], &(&1 * &1))
[1, 4, 9]
iex(2)> Enum.map([1,2,3], fn(x) -> x * x end)
[1, 4, 9]
iex(3)> List.foldl([1,2,3,4], 1, &(&1 * &2)) 
24
iex(4)> List.foldl([1,2,3,4], 1, fn(x, acc) -> x * acc end)
24

As you can see the amount of noise in the line has been reduced and the function that is called is much easier to read.

If you compare the function calls demonstrated above, you can see that for the call to List.foldl, &1 and &2 are the first and second argument of the function call.

Should the capture operator be used to create an anonymous function that itself relies on a named function and the arguments are sent in the right order, then the compiler will be able to optimise the function passing the arguments directly to the function reference:

iex(11)> ret = &(Float.round(&1, &2))
&Float.round/2
iex(12)> ret.(2.456, 2)
2.46

As you can see, the variable ret is a reference to the function Float.round/2. This is only the case when the arguments are passed in the correct order:

iex(13)> bad = &(Float.round(&2, &1))
#Function<12.90072148/2 in :erl_eval.expr/5>

here the compiler is unable to optimise as the arguments are not passed in order.

The capture operator has a second form:

 

Function Capture

If you pass a named function and arity to the capture operator, it will return an anonymous function. Any arguments passed to this anonymous function will be passed to the named function.

A by-product of this is that the named function can be captured and then this can be passed in as a helper function in an operation, potentially increasing readability and maintainability.

Using our trusty Float.round function from above:

iex(15)> rnd = &Float.round/2
&Float.round/2
iex(16)> rnd.(10.99, 0)
11.0

this way named functions can be wrapped in closures and we can use this for partial application and currying.