In part 3 of my Elixir journey I'll be sharing some about functions.
Functions
A simple function is defined using the `fn` keyword. Directly following you take the arguments followed by the -> symbol. Next the function body followed by the `end`
total = fn (a, b) -> a + b end
You would then invoke the function like this
total.(1, 2)
Even if the function doesn't take any parameters, you are still required to invoke it using parentheses.
word = fn -> IO.puts "hello" end
word.()
You can omit parentheses in the function definition (optional). I've personally found this less explicit but you might prefer it.
total = fn a, b -> a + b end
total.(3, 4)
It's important to note that when we pass parameters to a function Elixir is doing a match, not assignment like we might think traditionally.
total = fn (a, b) -> a + b end
total.(1, 2)
{a, b} = {1, 2}
A single function definition may have many different implementations. The order that these functions appear is important. Working top down the first that has a match for the arguments you are passing will win.
defmodule Foo do
def my_func({name}) do IO.puts("#{name}") end
def my_func({name, number}) do IO.puts("#{name} #{number}") end
end
You can follow along using IEx by throwing this script into a file called `foo.exs` and running the command below.
iex -S mix run foo.exs
IEx provides an interactive session that you can use to learn the language. To invoke the first function above pass only the name parameter.
Foo.my_func({"toran"})
To invoke the 2nd implementation pass both name and number.
Foo.my_func({"toran", 123})
Functions can return other functions. In the example below we first bind the `word` variable to the function returned.
word = fn -> fn -> "hello" end end
We can invoke this returned function to see the output "hello". Note: if we only invoke the first function we won't see the value "hello" so instead we must invoke it twice.
word.().()
To see the power of functions that return functions we should create a function for `add` that allows us to create new functions and call those with some value. In the example below we create a function that explicitly adds 3 to whatever you pass it.
add = fn (a) -> fn (b) -> a + b end end
To create the first function we only need to invoke the first function
add_three = add.(3)
Next we can use the add_three function to add any other number we pass
add_three.(4)
add_three.(5)
add_three.(6)
One trick to make the inner most function body more clear is to wrap it with parentheses
add = fn (a) -> (fn (b) -> a + b end) end
In the add examples above the `a` variable is bound in the scope of the outer function. But when the inner most function is defined, it will inherit this same scope... meaning we have just seen our first closure in action! You might have heard this referred to as lexical scoping. To learn more checkout the Elixir language documentation.
Because functions are just values in Elixir, we can pass them to other functions.
add_one = fn (a) -> a + 1 end
apply = fn (fun, value) -> fun.(value) end
apply.(add_one, 9)
In the example above we create a function `add_one` and then pass it to another function `apply` that invokes it and returns the value 10
Below is another example of this behavior using the Enum module to transform a list of numbers.
list = [1, 2, 3]
Enum.map(list, fn (e) -> e + 1 end)
This will enumerate the list and return a new list with values [2, 3, 4]