In every object oriented programming language we model the domain with classes. Every object we generate from our classes is a container of state represented by your instance variables, and behavior, represented by your methods. But Elixir is a functional programming language therefore it doesn’t have classes. It also doesn’t allow you to store state in objects. But of course it allows the definition of behavior, using functions.
Let’s start with the description of the most common scenario: two simple named functions inside a module.
defmodule Names do
def full_name(name, surname) do
name <> "" <> surname
end
def capitalized_name(name) do
String.capitalize(name)
end
end
A module is nothing more than a functions container, a way to group functions together. Our module Names
, defined above, groups together the functions full_name/2
and capitalized_name/1
.
Like with classes, in a module we can set the visibility of a function: using def
we define a function that is available to all callers, using defp
we define a function that is visible only inside the module itself.
In a module we can define multiple functions with the same name, and then leverage on pattern matching for them to be selected, as you can see in the following example:
defmodule Names do
def full_name(name) do
String.capitalize(name)
end
def full_name(name, surname) do
String.capitalize(name) <> " " <> String.capitalize(surname)
end
end
Names.full_name("john") # John
Names.full_name("john", "doe") # John Doe
Names.full_name("john", "doe", "jr") # (UndefinedFunctionError) undefined function: Names.full_name/3
A function may also have default arguments:
defmodule Names do
def full_name(name, surname, title \\ "Mr") do
title <> " " <> String.capitalize(name) <> " " <> String.capitalize(surname)
end
end
Names.full_name("john", "doe") # Mr John Doe
Names.full_name("melissa", "doe", "Ms") # Ms Melissa Doe
Elixir supports function clauses, as explained in the following example:
defmodule Names do
def name(name, married) when married == true do
"Mrs" <> " " <> String.capitalize(name)
end
def name(name, married) when married == false do
"Ms" <> " " <> String.capitalize(name)
end
end
Names.name("melissa", true) # Mrs Melissa
Names.name("Hilary", false) # Ms Hilary
After the when
statement of the function definition we restrict the pattern matching to the evaluation of the clause, based on the argument value. If the clause is not matched, Elixir proceeds to the following function evaluation in order to find a match, throwing an error if it doesn’t find any.
While referring to a function in this post, I frequently used the function_name/arity
notation, where arity is just the number of parameters for the defined function. Turns out this notation is also useful in case of function capturing, to store the function in a variable:
defmodule Names do
def full_name(name, surname) do
String.capitalize(name) <> " " <> String.capitalize(surname)
end
end
fun = &Names.full_name/2
# &Names.full_name/2
fun.("John", "Doe")
# "John Doe"
That’s all for today.
More information on this topics are available in the official Elixir modules guide. Taking a look is worth the effort.
Comments