Functions


Functions are the lifeblood of Elixir. It should be obvious that functions play a large role in functional programming. Some people may view this as an obstacle, fearing that functional programming is inherently math-based and therefore more difficult. I can assure you this is not the case. Functional programming is not any more difficult than object-oriented programming. There is a paradigm shift that requires a change in thinking and reasoning about your code. However, once you begin thinking in functional terms and structuring your programs according to best practices, you'll find yourself to be a stronger and more powerful programmer. The core objective in programming is to receive some input, perform some transformations, then produce some output. This is exactly what functions do. In Elixir, there are named functions and anonymous functions. We'll begin by exploring anonymous functions.

Anonymous functions

"You can't spell fun without fn"

Anonymous functions in Elixir are start with the letters fn. They can be bound to a variable with the following syntax: variable = fn (head) -> body end. Notice the anonymous function consists of two parts, head and body, which are separated by a forward arrow. The function head contains the necessary parameters while the body contains some sort of transformation logic. The end of the function is marked by the end keyword. Putting this all together let's create a simple function that adds two values:

iex(1)> sum = fn (a, b) -> a + b end
#Function<12.99386804/2 in :erl_eval.expr/5>

iex(2)> sum.(5,6)
11

Note how when we are calling the function we use a ( . ) dot between the anonymous function and arguments. The function definition on line 1 follows the example syntax exactly. We bind our function to the variable "sum". The function takes two arguments, "a" and "b". The -> arrow can be read as, "yields". Following the arrow is the function body. In the body we declare the functions logic, which is what exactly we want our function to do with the arguments "a" and "b" when it receives them. In this simple example, the "sum" function will add the values together. After defining our function in iex, we are given a return value. This value isn't terribly important. All that matters is that we have, indeed, defined a function and no errors have been returned.

Passing functions

In Elixir, functions are considered "first-class" citizens. In programming terms, this simply means that a function can be passed as an argument to another function. Although we bound our function to a variable previously, it's not uncommon to see an unbound anonymous function passed as an argument. For example, the Enum module (short for Enumerable) is a widely used module for working with collections. For many transformations, it takes a collection as the first argument and a function as the second argument.

# create a collection
iex(3)> list = [1,2,3,4]
[1, 2, 3, 4]

# pass in the collection and a function
iex(4)> Enum.map(list, fn (x) -> x * 10 end)
[10, 20, 30, 40]

Notice how we passed two arguments to Enum.map, a collection of elements and a function.

The capture operator (&)

Functions are considered first class citizens in Elixir. This means that they can receive other functions as parameters and also return functions as the values from other functions. Anonymous functions are so ubiquitous that Elixir has a shortcut for creating them, the capture operator (&). Our example above can be rewritten using the & operator like so:

iex> sum = &(&1 + &2)

iex> sum.(5, 6)

11

In the example above, we are setting the unbound variable "sum" to an anonymous function that takes two parameters and adds them together. This shorthand syntax would be difficult to understand without knowing how the capture operator works. When the & operator appears on the right hand side of the equals sign it is indicating to us, "I'm creating an anonymous function!". Inside the parenthesis, &1 refers to the first argument and &2 refers to the second argument. Instead of declaring the parameters and function logic separately, the capture operator declares them together. In this case, we define a function that takes in two parameters and adds them together. Once you understand the syntax, it becomes easy to look at the shorthand anonymous function and know exactly what it does. For instance, see if you can figure out what this next function does:

iex> func = &(&1 * &1)

iex> func.(2)

# what will the result be?

It should be obvious what the result of the above function will be. We are creating an anonymous function where we take in one parameter and multiply it by itself. In other words, we square it. The result of passing func the argument of "2" would be "4".

You may be wondering why & is called the capture operator. Along with being the shorthand for defining an anonymous function, the capture operator can also "capture" a named function. Let's demonstrate this by capturing the upcase function of the String module.

iex> fun = &String.upcase/1
&String.upcase/1 

iex> fun.("hello")
"HELLO"

By using the & operator we were able to capture the String.upcase function, storing it in the variable "fun". Note that we must pass the functions arity as well. The arity refers to the number of arguments the function takes. Once stored in our "fun" variable, we can invoke the function using the dot ( . ) syntax, passing in the string to be "upcased".

Named functions

Functions in Elixir must reside within modules. Functions are invoked using the module name and function name separated by a dot ( . ). For example, String.downcase("NAME"), where "String" is the module, "downcase" is the function, and the "NAME" is the argument for which the function should be applied.

Interactive Elixir (1.6.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> String.downcase("NAME")
"name"

Another example could be separating a string into its individual letters:

iex(2)> String.graphemes("elixir")
["e", "l", "i", "x", "i", "r"]

In Elixir, strings are UTF-8 encoded binaries. The _graphemes _refer to the individual characters, which is why the function split our string "elixir" into a list of letters. The downcase and graphemes functions are built-in functions of the String module. To create a new named function, we use the def construct, followed by the function name. Here is the syntax:

def name (arg1, arg2) do
  # write some code
end

Functions are defined using def, followed by the name of the function. Similar to anonymous functions, named functions also have a head and body. The function head is defined on the first line, a named function followed by the expected arguments. In our example above, we have a function that takes in two arguments, "arg1" and "arg2". The function body is delimited by the words do and end. Inside the body, we have access to the arguments passed in, just like in anonymous functions. We can rewrite our "sum" function from above using a named function, like so:

defmodule Arithmetic do

  def sum(a, b) do
    a + b
  end

end

We can then call the "sum" function like this, Arithmetic.sum(1, 2), which gives us a result of 3.

Function arity

You'll often see function names in Elixir, as well as Erlang, written like Enum.map/2 or String.length/1. The "arity" refers to the number that follows the function name, /2 for Enum.map and /1 for String.length. This number refers to the arguments the function expects. A fully qualified function names consists of the Module, function and arity. Two functions that reside in the same module and share the same name but have different arities are considered two separate functions.

Default arguments

Default values can be provided for each argument a function expects. If the function is called without any arguments, the default arguments are used. If the function is called with arguments, the default values are ignored. Default arguments follow the expected argument and are denoted by two backslashes ( ).

Private functions

The defp macro defines a private function. These functions can only be called locally (from the modules in which they are defined).

Function signatures

In Elixir, multiple functions can be defined with the same name AND be in the same module as long as they have different signatures. This ability leads to concise functions with specific behavior. We'll see an example of this in the Guards chapter, where multiple functions are named "double".

results matching ""

    No results matching ""