Elixir

Initially I thought I could write a 10 page Elixir introduction with this chapter. Just to provide the basics to get you going for using the Phoenix and the Ash Framework. All that by avoiding any chicken-or-egg situations - where an explanation relies on concepts yet to be discussed.

I failed! I ended up with a chapter about Elixir which covers a lot. It is meant to be read in a linear fashion. But feel free to just skip parts and come back later (or don’t).

If this is your first functional programming language you might want to get a cup of coffee first. It might take a while to get used to the functional programming paradigm. It took me a long time too!

Elixir Version

Our journey is charted for Elixir version 1.15. Please use the following command to check the installed Elixir version:

$ elixir -v
Erlang/OTP 26 [erts-14.0] [source] [64-bit] [...]

Elixir 1.15.0 (compiled with Erlang/OTP 24)
If you’re new to Elixir or in need of an upgrade, you might want to consider using asdf, a versatile version manager that can handle both Elixir and Erlang (you need both). Head over to the asdf homepage for instructions on how to install and use it.

Elixir’s Interactive Shell (IEx)

Elixir equips you with an interactive shell, IEx (Interactive Elixir), that’s going to be our sandbox for a lot of examples in this chapter. So let’s roll up our sleeves and give it a whirl in your command line:

$ iex
Erlang/OTP 26 [erts-14.0] [source] [64-bit] [...]

Interactive Elixir (1.15.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> (1)
1This is your IEx prompt.
To bid goodbye to IEx, give CTRL-C a double tap or a single CTRL-\ will do.
IEx provides autocomplete (just tap TAB when you’re unsure) and a command history (the arrow-up key is your friend).

Do you like video tutorials? Have a look at the "IEx Basics in 55 Seconds" video in our @elixir-phoenix-ash YouTube Channel.

Hello world!

The classic! We use iex to run the function IO.puts/1 which prints a string to standard output:

iex> IO.puts("Hello world!")
Hello world!
:ok

Always enclose strings with double quotes! Single quotes create a charlist, which is a different type. In case you need double quotes within a string you have to escape them with backslashes:

iex> IO.puts("With double quotes: \"Hello world!\"")
With double quotes: "Hello world!"
:ok

Let’s have a quick look at IO.puts/1:

  • IO is the name of the IO module. A module is collection of functions. It is a way to organize code. Normally a module has to be loaded with require or import but since the IO module is so essential it gets loaded automatically.

  • puts/1 is the name of a function. The final 1 of IO.puts/1 is called a Function Arity. The arity represents the number of arguments that function accepts. A module can contain multiple functions with the same name as long as they all have a different arity.

We discuss modules and functions more detailed in modules and functions.

Debugging Essentials

In this introductory guide, we’re only scratching the surface of debugging in Elixir, but I’d like to introduce three vital tools that will be beneficial while exploring the code examples in this book.

dbg/2

Elixir version 1.14 introduced the powerful debugging tool dbg/2. It not only prints the passed value, returning it simultaneously, but also outlines the code and location. Here’s an example:

iex(1)> name = "Elixir"
"Elixir"
iex(2)> dbg(name)
[iex:2: (file)]
name #=> "Elixir"

"Elixir"
iex(3)> dbg(IO.puts("Hello World!"))
Hello World!
[iex:3: (file)]
IO.puts("Hello World!") #=> :ok

:ok
Beginners often find it odd that dbg/2 and IO.inspect/2 return the value they print. However, once you start utilizing Pipes (which we’ll discuss in The Pipe Operator (|>)), it becomes a natural part of maintaining uninterrupted code flow while inspecting values in a pipe operation.
Have you recognized that dbg/2 has an arity of 2 but we only use one argument? That is possible because the second argument is optional (hence the options \\ [] part).

IO.inspect/2

The function IO.inspect(item, opts \\ []) is a staple in Elixir debugging. Although it’s less feature-rich than dbg/2, its usage remains widespread, given its history and straightforward application. You can inject IO.inspect/2 into your code at any point, printing the value of an expression to your console - perfect for verifying a variable’s value or a function call’s result.

For example:

iex> name = "Elixir"
"Elixir"
iex> IO.inspect(name)
"Elixir"
"Elixir"
Feel free to always use dbg/2 instead of IO.inspect/2. However, if you’re working with older codebases, you’ll likely encounter IO.inspect/2.

i/1

Finally, the IEx helper function i/1 offers useful insights about any data type or structure. Launch an IEx session with iex in your terminal, and then call i() with any term to obtain information about it.

Here’s an example:

iex> name = "Elixir"
"Elixir"
iex> i(name)
Term
  "Elixir"
Data type
  BitString
Byte size
  6
Description
  This is a string: a UTF-8 encoded binary. It's printed surrounded by
  "double quotes" because all UTF-8 encoded code points in it are printable.
Raw representation
  <<69, 108, 105, 120, 105, 114>>
Reference modules
  String, :binary
Implemented protocols
  Collectable, IEx.Info, Inspect, List.Chars, String.Chars

This output elucidates that "Hello, world!" is a 13-byte BitString and provides further details like the string’s raw representation and the protocols it implements.

Modules and Functions

In Elixir, we organize code in a structured way using modules and functions.

For Elixir beginners we now run into a typical chicken and egg problem. Because how can I show you how Modules and Functions work while you have never used any Elixir code? I try to use code examples which should be easy to understand.

Modules are like containers for functions, which represent reusable pieces of code.

Let’s illustrate this with an example:

iex> defmodule Greetings do (1)
...>   def hello(name) do (2)
...>     "Hello, " <> name <> "!" (3)
...>   end
...> end
{:module, Greetings,
 <<...>>, {:hello, 1}} (4)
iex> Greetings.hello("Alice") (5)
"Hello, Alice!"
1We use the defmodule keyword to define a module. The module’s name always begins with a capital letter.
2We define a function within the module using the def keyword.
3The function concatenates the input name with a greeting message.
4The return value represents the module creation.
5We call the function from outside the module using this syntax.
Both defmodule and def use a do …​ end structure to denote the start and end of the block.
Module names use CamelCase and start with a capital letter, while function names are in snake_case.

As an exercise, let’s save the following module to the file greetings.exs:

defmodule Greetings do
  def hello(name) do
    "Hello, " <> name <> "!"
  end
end

To use the hello/1 function in the Greetings module, we need to load and compile greetings.exs with Code.require_file("greetings.exs") in iex:

$ iex
Erlang/OTP 26 [erts-14.0] [source] [64-bit] [...]

Interactive Elixir (1.15.0) - press Ctrl+C to exit (...)
iex(1)> Code.require_file("greetings.exs")
[
  {Greetings,
   <<70, 79, ...>>}
]
iex(2)> Greetings.hello("Bob")
"Hello, Bob!"

Function Arity

In Elixir, the term "arity" refers to the number of arguments that a function accepts. Functions are identified by both their name and their arity, expressed as name/arity.

The arity concept is essential because it allows us to define multiple functions with the same name but different arities within a single module. Each of these is considered a distinct function due to its unique argument count.

Let’s demonstrate this with a slightly more complex example:

iex> defmodule Greeting do
...>   def greet() do (1)
...>     "Hello, world!"
...>   end
...>
...>   def greet(name) do (2)
...>     "Hello, #{name}!"
...>   end
...>
...>   def greet(name, time_of_day) do (3)
...>     "Good #{time_of_day}, #{name}!"
...>   end
...> end

iex> Greeting.greet()
"Hello, world!"
iex> Greeting.greet("Alice")
"Hello, Alice!"
iex> Greeting.greet("Bob", "morning")
"Good morning, Bob!"

In this example, we’ve defined three versions of the greet function, each with a different arity.

1The greet/0 function, which takes no arguments, returns a generic greeting.
2The greet/1 function accepts one argument and provides a personalized greeting.
3The greet/2 function takes two arguments and offers a personalized greeting that also includes the time of day.

Private Functions

Sometimes, we want to hide certain functions within a module, making them inaccessible from outside. Elixir supports this through private functions, which we declare using the defp keyword:

iex> defmodule SecretMessage do
...>   def public_message(name) do
...>     secret() <> name
...>   end
...>
...>   defp secret do (1)
...>     "Secret Hello, "
...>   end
...> end

iex> SecretMessage.public_message("Alice") (2)
"Secret Hello, Alice"
iex> SecretMessage.secret (3)
** (UndefinedFunctionError) function SecretMessage.secret/0 is undefined or private
    SecretMessage.secret()
1secret/0 is a private function and can only be accessed within its module.
2public_message/1 is public, and can be called from outside its module. It can access secret/0 because they’re in the same module.
3Attempting to call secret/0 from outside its module results in an UndefinedFunctionError because it’s a private function.

Private functions help us hide implementation details and reduce the exposed interface of a module, leading to cleaner and more maintainable code.

Hierarchical Modules

As your projects become more complex, it’s crucial to structure your code into a clear and manageable form. In Elixir, you can achieve this by using hierarchical module names. Hierarchical modules are defined by attaching sub-module names to a parent module using a . delimiter.

Here’s a new example with a Fruit Shop:

iex> defmodule FruitShop.Apples do
...>   def price_per_kg() do
...>     10
...>   end
...> end

iex> FruitShop.Apples.price_per_kg()
10

The . syntax offers a neat shortcut for defining nested modules. Here’s how you can create the same hierarchy using nested module definitions:

iex> defmodule FruitShop do
...>   defmodule Apples do
...>     def price_per_kg() do
...>       10
...>     end
...>   end
...> end

iex> FruitShop.Apples.price_per_kg()
10

Both methods achieve the same result. Your choice between these two depends on your project’s structure and your coding style preference.

Import

The import directive in Elixir provides a way to access public functions from other modules without needing to write out their fully qualified names. This can make your code cleaner and easier to read.

Consider the following FruitShop.Apples module:

iex> defmodule FruitShop.Apples do
...>   def price_per_kg() do
...>     10
...>   end
...> end

By importing this module, you can call its functions directly, without having to prefix them with the module’s name:

iex> import FruitShop.Apples
FruitShop.Apples
iex> price_per_kg()
10

Here, importing FruitShop.Apples lets you call price_per_kg/0 directly, eliminating the need to use the FruitShop.Apples. prefix.

Selective Importing

While importing a module grants you access to all its public functions, there might be times when you want to import only specific functions from a module. Elixir allows you to do this using a selective import.

For instance, suppose the FruitShop.Apples module also had a quantity_in_stock/0 function. But if you only needed price_per_kg/0 in your current context, you could import just that function like so:

iex> defmodule FruitShop.Apples do
...>   def price_per_kg() do
...>     10
...>   end
...>   def quantity_in_stock() do
...>     100
...>   end
...> end

iex> import FruitShop.Apples, only: [price_per_kg: 0]
FruitShop.Apples
iex> price_per_kg()
10

Here, import FruitShop.Apples, only: [price_per_kg: 0] means that only the price_per_kg/0 function from FruitShop.Apples is available for direct calling. This can help reduce naming conflicts and makes it clear which functions are being used from the imported module.

An alternative to only is except, which lets you import all functions except the ones specified.

Alias

The alias directive offers a convenient way to assign a shorter, alternative name to a module. This can improve both readability and maintainability of your code by reducing verbosity when accessing the module’s functions.

Take a look at the FruitShop.Apples module:

iex> defmodule FruitShop.Apples do
...>   def price_per_kg() do
...>     10
...>   end
...> end

To make calling this module’s functions less verbose, you can use the alias directive to assign it a shorter name:

iex> alias FruitShop.Apples, as: Apples
FruitShop.Apples
iex> Apples.price_per_kg()
10

In the code above, we’ve created an alias for FruitShop.Apples as Apples.

For a quicker and more direct way, you can simply use alias FruitShop.Apples. Elixir will automatically infer the alias from the last part of the module name, in this case Apples:

iex> alias FruitShop.Apples
FruitShop.Apples
iex> Apples.price_per_kg()
10

In this example, the alias FruitShop.Apples directive lets you call functions from FruitShop.Apples using the shortened name Apples. This can significantly improve readability when working with modules that have long or complex names.

Use

Elixir’s use keyword is a cornerstone of metaprogramming in Elixir. It is a powerful tool that helps keep our code DRY (Don’t Repeat Yourself) by allowing us to perform certain actions defined in another module within the current module.

I won’t delve too deeply into metaprogramming here. I’m simply covering it to ensure that you can recognize it when you come across it unexpectedly.

Metaprogramming is a technique that helps us write code that generates or modifies other code. In the context of Elixir, we can think of the use keyword as a way to inject code from one module into another. This is accomplished through the use of the using macro in the module that is being used.

Let’s illustrate this with a more comprehensive example involving a Discount module and two fruit modules, Apples and Bananas:

defmodule Discount do
  defmacro __using__(_) do
    quote do
      def apply_discount(price, percentage) do
        price - (price * (percentage / 100))
      end
    end
  end
end

defmodule FruitShop.Apples do
  use Discount

  def price_per_kg() do
    10
  end
end

defmodule FruitShop.Bananas do
  use Discount

  def price_per_kg() do
    5
  end
end

In these examples, both the FruitShop.Apples and FruitShop.Bananas modules use the Discount module. The use keyword triggers the using macro in the Discount module, which in turn injects the apply_discount/2 function definition into the FruitShop.Apples and FruitShop.Bananas modules. Therefore, we can call apply_discount/2 directly on either of these modules:

iex> FruitShop.Apples.apply_discount(10, 20)
8

iex> FruitShop.Bananas.apply_discount(5, 15)
4.25

In these cases, we’ve applied a 20% discount to the price of apples (which was 10), and the result is 8. Similarly, we’ve applied a 15% discount to the price of bananas (which was 5), and the result is 4.25.

By leveraging the power of the use keyword and metaprogramming, we’ve written a versatile Discount module that can be used across multiple fruit modules to apply discounts to their prices.

If you’re working with a Phoenix application, you might see use ExUnit.Case in your test files. This is a practical example where ExUnit.Case provides a set of functionalities (like assert functions) that will be accessible within your test cases.

Higher-Order Functions

In Elixir, functions are treated as first-class citizens. This means you can use functions as arguments to other functions, and even return them as results. A function that can take another function as an argument or return it as a result is called a higher-order function.

When passing a function to a higher-order function, we often use anonymous functions. Let’s dive in and understand what these are.

Anonymous Functions

An anonymous function, as the name suggests, is a function without a name. These are throwaway functions that you define right where you need them.

Anonymous functions are defined using the fn keyword, like so:

iex> hello = fn -> "Hello, world!" end (1)
#Function<20.99386804/0 in :erl_eval.expr/5>
iex> hello.() (2)
"Hello, world!"
1We’re defining an anonymous function that returns "Hello, world!" and assigning it to the variable hello.
2The . (dot) operator is used to call anonymous functions.

Anonymous functions can also take parameters:

iex> add = fn (a, b) -> a + b end (1)
#Function<12.99386804/2 in :erl_eval.expr/5>
iex> add.(1, 2) (2)
3
1We define an anonymous function that accepts two parameters and returns their sum.
2This anonymous function can be invoked with two numbers to get their sum.
Using anonymous functions in Elixir is often easier with the Capture Operator & which is a shorthand version.

Variables

In Elixir, variable names adhere to the snake_case format. They start with a lowercase letter, and words are separated by underscores (_).

Take a look at the following examples:

iex> length = 10 (1)
10
iex> width = 23
23
iex> room_area = length * width
230
1Here, the = operator assigns the value 10 to the variable length.

Variable names starting with an uppercase letter will throw an error:

iex> RoomWidth = 2
** (MatchError) no match of right hand side value: 2 (1)
1The MatchError might seem strange at this point, but fear not! Its mystery will unravel as we dive deeper into Elixir’s realm and learn about pattern matching.

Variable Scopes

Variable scope is a fundamental concept in Elixir, referring to the area of code where a variable can be accessed or is valid. To better understand how variable scopes work in Elixir, let’s consider the following example using fruits.

defmodule FruitShop do
  def fruit_count do
    apples = 10 (1)
    IO.puts("Apples in the shop: #{apples}")

    basket_fruits() (2)

    IO.puts("Apples in the shop: #{apples}") (4)
  end

  defp basket_fruits do
    apples = 5 (3)
    IO.puts("Apples in the basket: #{apples}")
  end
end
1Here, we declare the number of apples in the shop as 10.
2We then call the basket_fruits/0 function.
3Inside the basket_fruits/0 function, we declare the count of apples in the basket as 5.
4After coming back from the basket, we check the count of apples in the shop again.

The output of calling FruitShop.fruit_count() would be:

Apples in the shop: 10
Apples in the basket: 5
Apples in the shop: 10

As you can see, the number of apples in the basket_fruits/0 function did not affect the number of apples in the fruit_count/0 function. This is because, although they have the same name (apples), they are in different scopes and, hence, are treated as completely different variables.

Immutability

Probably you have already heard about immutability in Elixir. What’s that about?

A variable points to a specific part of the memory (RAM) where the data is stored. In many programming languages that data can be changed to update a variable. In Elixir, you can’t change it. But that doesn’t mean that you can’t rebind a variable to a different value just that this new value gets a new piece of memory and doesn’t overwrite the old memory. They both coexist. Once a function returns a result and therefore, has finished its work, everything gets garbage collected (wiped blank).

Why is that important at all? With immutable variables, we can be sure that other processes can not change their values while running parallel tasks. That has a massive effect. In the end, it means that your Phoenix application can run on multiple CPUs on the same server in parallel. It even means that your Phoenix application can share multiple CPUs on several nodes of a server cluster in your data center; this makes Elixir extremely scalable and safe.

But doesn’t that make your application slower? Funny thing: No. This way is faster. Since it is not efficient to change data in memory.

But don’t worry. It is not as complicated as it sounds. Everytime you use a variable it uses the value of that moment in time. It will not be effected/changed afterwords:

iex> product = "Orange"
"Orange"
iex> test1 = fn -> IO.puts(product) end (1)
#Function<21.126501267/0 in :erl_eval.expr/5>
iex> product = "Apple"
"Apple"
iex> test2 = fn -> IO.puts(product) end
#Function<21.126501267/0 in :erl_eval.expr/5>
iex> product = "Pineapple"
"Pineapple"
iex> test3 = fn -> IO.puts(product) end
#Function<21.126501267/0 in :erl_eval.expr/5>
iex> product = "Banana"
"Banana"
iex> test1.() (2)
Orange
:ok
iex> test2.()
Apple
:ok
iex> test3.()
Pineapple
:ok
iex> IO.puts(product)
Banana
:ok
1Those anonymous functions can run on totally different CPUs. Each one lives in its own little universe.
2The value of product has changed multiple times. But for test1.() it is the value from that point in time when we created the function.
Do you have to understand immutability to learn Elixir? No! Don’t stress yourself. It is a concept that you will learn over time. Just keep it in mind that it is there.

Control Structures

Control structures are a fundamental aspect of any programming language, and Elixir is no exception. They direct the flow of execution and help us build more complex and powerful programs.

You will not use all of these control structures regularly. Have a look and decide which ones feel useful to you.

if, unless, and else

if, unless, and else are coding classics to create conditional branches which are used in most programming languages. These expressions evaluate to either true or false and execute the associated code blocks.

Two styles of syntax can be used for these expressions: multi-line and single-line.

Multi-line Syntax

For complex conditions or actions, or when including an else clause, the multi-line syntax is most appropriate. It uses do…​end to wrap the executed code block.

# `if` expression
iex> num = 5
iex> if num > 3 do
...>   IO.puts("#{num} is greater than 3")
...> end

# `unless` expression
iex> num = 2
iex> unless num > 3 do
...>   IO.puts("#{num} is not greater than 3")
...> end

# `if` with `else` expression
iex> num = 2
iex> if num > 3 do
...>   IO.puts("#{num} is greater than 3")
...> else
...>   IO.puts("#{num} is not greater than 3")
...> end

Single-line Syntax

In a single-line syntax, the do: keyword follows the condition. This syntax is often used for simple conditions and actions.

# `if` expression
iex> num = 5
iex> if num > 3, do: IO.puts("#{num} is greater than 3")

# `unless` expression
iex> num = 2
iex> unless num > 3, do: IO.puts("#{num} is not greater than 3")

Both styles are equally valid; the choice depends on the specific use case and code readability.

Remember, if, unless, and else are expressions, not statements. They always return a value, which can be assigned to a variable or used in a larger expression.

if/2 is a Macro

In Elixir, if/2 is a macro, a special kind of function that is executed at compile-time, before your program runs. It gets "translated" into a case/2 expression internally. This doesn’t have to bother you. I just felt that it was good to know. In case you are more interested in this detail have a look at

Understanding if/2 Arity

In Elixir, when we say a function or macro has an arity of 2, we mean it accepts two arguments. The if macro in Elixir has an arity of 2 because it requires two arguments to work correctly: a condition and a keyword list.

You are now familiar with the if construct looking something like this:

if condition do
  # Code executed when the condition is true
else
  # Code executed when the condition is false
end

This is the most common way to use if in Elixir, and it’s very readable. However, under the hood, Elixir is interpreting this in a slightly different way. This 'do/else' style is actually syntactic sugar, a way to make the code look nicer and easier to understand.

In reality, Elixir sees the if construct as follows:

if(condition, do: # Code to execute if true, else: # Code to execute if false)

Here, it’s clear to see that if is receiving two arguments:

  1. The condition to evaluate, which should be either true or false.

  2. A keyword list that specifies what to do: if the condition is true and what to do else: if the condition is false.

So when we say if/2, we’re saying the if macro with two arguments: a condition and a keyword list.

case

The case construct in Elixir provides a powerful way to pattern match complex data structures and execute code based on the matching pattern. For many Elixir porgrammers it is the go-to construct for control flow.

A case expression evaluates an expression, and compares the result to each pattern specified in the clauses. When a match is found, the associated block of code is executed.

iex> num = 2
iex> case num do
...>   1 ->
...>     IO.puts("One")
...>   2 ->
...>     IO.puts("Two")
...>   _ ->
...>     IO.puts("Other")
...> end
Two

In the above example, num is evaluated and its value is compared with each pattern. The pattern 2 matches the value of num, so "Two" is printed.

A catch-all clause (_ →) is often used as the last clause to handle any values not explicitly covered by previous patterns.

Pattern matching in case is not limited to simple values. You can also pattern match on more complex structures like tuples, lists, or maps.

iex> tuple = {:ok, "Success"}
iex> case tuple do
...>   {:ok, msg} ->
...>     IO.puts("Operation successful: #{msg}")
...>   {:error, reason} ->
...>     IO.puts("Operation failed: #{reason}")
...>   _ ->
...>     IO.puts("Unknown response")
...> end
Operation successful: Success

In this example, the case statement matches on the structure and content of the tuple.

Remember, like if and unless, case is an expression, meaning it returns a value which can be assigned to a variable or used in another expression.

iex> num = 3
iex> result = case num do
...>   1 ->
...>     "One"
...>   2 ->
...>     "Two"
...>   _ ->
...>     "Other"
...> end
iex> IO.puts(result)
Other

In this example, we use a case expression to evaluate num and assign the corresponding string to the result variable. The variable result is then printed using IO.puts/1. The case expression returns "Other", because num does not match 1 or 2, and "Other" is assigned to result.

Importance of Pattern Order

A critical aspect to understand when using case is the order of pattern matches. Elixir evaluates the patterns from top to bottom and executes the first pattern that matches, ignoring any subsequent patterns even if they are more precise.

Let’s illustrate this with an example:

iex> tuple = {:ok, "Success"}
iex> case tuple do
...>   {:ok, _} ->
...>     IO.puts("Operation was OK")
...>   {:ok, msg} ->
...>     IO.puts("Operation successful: #{msg}")
...>   _ ->
...>     IO.puts("Unknown response")
...> end
Operation was OK

In this example, even though the second pattern {:ok, msg} is a better match for tuple (as it also matches and binds the message), the first pattern {:ok, _} matches first and so its associated code is executed.

Therefore, when using case, it’s important to order your patterns from the most specific to the least specific to ensure the intended pattern is matched first.

cond

cond is another control structure in Elixir that checks for the truthiness of multiple conditions. It is like a collection of multiple if/2 expressions. It evaluates each condition in turn, from top to bottom, and once it encounters a condition that evaluates to true, it executes the associated block of code and ignores the rest.

Here’s an example:

iex> num = 15
iex> cond do
...>   num < 10 ->
...>     IO.puts("#{num} is less than 10")
...>   num < 20 ->
...>     IO.puts("#{num} is less than 20 but greater than or equal to 10")
...>   true ->
...>     IO.puts("#{num} is greater than or equal to 20")
...> end
15 is less than 20 but greater than or equal to 10

In this example, cond checks each condition in order. When it finds a truthy condition (num < 20), it executes the associated block of code and skips the rest.

The true → clause serves as a catch-all clause, similar to the _ → in a case expression. If none of the previous conditions are truthy, the code associated with the true → clause will be executed.

cond is especially useful when you have multiple conditions and don’t want to write nested if statements.

Remember, cond is an expression and it returns a value, which can be assigned to a variable or used in another expression.

with and for

with Keyword

The with keyword in Elixir is used for a sequence of pattern matches, returning the value of the last expression if all previous matches succeed.

Here’s an example:

iex> fruits = [{:apple, 5}, {:orange, 3}, {:banana, 4}]
iex> with {:apple, count} <- List.keyfind(fruits, :apple, 0) do
...>   IO.puts("Found #{count} apples!")
...> else
...>   _ -> IO.puts("No apples found.")
...> end
Found 5 apples!

In this example, we have a list of tuples representing different kinds of fruits and their respective counts. We use the with keyword to pattern match on an {:apple, count} tuple.

If the pattern match is successful, we print a message saying we found a certain number of apples. If the pattern match fails, we fall back to the else clause and print a message saying no apples were found.

for Comprehensions

for comprehensions in Elixir provide a way to iterate over Enumerables and generate a new list, optionally filtering the elements. The result of this operation can be assigned to a variable.

Here’s an example:

iex> squared_numbers = for num <- [1, 2, 3, 4, 5],
...>     do: num * num
[1, 4, 9, 16, 25]
iex> squared_numbers
[1, 4, 9, 16, 25]

In this example, for iterates over each number in the list [1, 2, 3, 4, 5], squares each number (num * num), and collects the results into a new list. This list is then assigned to the variable squared_numbers.

You can also filter elements in a for comprehension using a guard clause:

iex> odd_squares = for num <- [1, 2, 3, 4, 5],
...>     rem(num, 2) == 1,
...>     do: num * num
[1, 9, 25]
iex> odd_squares
[1, 9, 25]

In this example, only odd numbers are squared. The rem(num, 2) == 1 condition filters out even numbers, so they are not included in the result. This resulting list is assigned to the variable odd_squares.

Variable Assignment with Control Structures

One of the unique characteristics of control structures in Elixir is their ability to return a value, which can be assigned to a variable. This is possible because these expressions always return a value.

Here’s an example of assigning the result of an if expression to a variable:

iex> num = 5
iex> comparison_result = if num > 3 do
...> "#{num} is greater than 3"
...> else
...> "#{num} is not greater than 3"
...> end
iex> IO.puts(comparison_result)
5 is greater than 3

This approach can be extended to other control structures such as unless, case, and even custom defined ones:

iex> num = 2
iex> comparison_result = case num do
...> 1 -> "one"
...> 2 -> "two"
...> _ -> "other"
...> end
iex> IO.puts(comparison_result)
two

The last expression executed within the block will be the returned value of the control structure. If the condition does not pass and there is no else clause, the expression returns nil`.

Data Types

Since this is a beginners book I have to discuss all these data types because you’ll use them eventually. But you don’t need to understand them all right away. Feel free to skip the parts which don’t interest you. You can always come back later.

Integer

Integers are whole numbers that can be positive, negative or zero. In Elixir, you can use integers without any limits, as long as you have enough memory in your machine.[1]

Here is an example of integers in Elixir:

iex> 3
3

iex> -1042
-1042

iex> 0
0

Integers can also be represented in binary, octal, and hexadecimal:

iex> 0b1010
10

iex> 0o777
511

iex> 0x1F
31

Additionally, Elixir supports basic arithmetic operations with integers:

iex> 2 + 3
5

iex> 10 - 7
3

iex> 5 * 4
20

iex> 16 / 2
8.0 (1)
1The division operation (/) in Elixir always returns a float.

Readability in Large Integers

When working with large integers in Elixir, it’s common to encounter readability issues. A long, continuous string of digits can be difficult to read at a glance, especially when it comes to distinguishing thousands, millions, billions and so on.

To improve the readability of large integers, Elixir allows the use of underscores (_) as visual separators. You can place these underscores anywhere in your number, and they will be ignored by the compiler.

Here’s an example:

iex> 1_000_000
1000000

In the example above, 1_000_000 is exactly the same as 1000000 in Elixir. The underscores simply make it easier to identify the number as one million at a glance.

This feature is particularly useful when working with very large numbers, or when defining constants that might be better understood in their 'grouped' form.

Remember that while you can place the underscores anywhere in the number, placing them in positions that reflect common numeric groupings (e.g., thousands, millions) tends to be the most effective for readability.

iex> 123_456_789 # easy to read
123456789

iex> 1234_5678_9 # harder to read
123456789

Float

Floats are numbers that have a decimal point. They are represented in Elixir using 64-bit double-precision.

Here’s how you can represent float numbers in Elixir:

iex> 3.14
3.14

iex> -0.1234
-0.1234

iex> 0.0
0.0

Floats in Elixir must be at least 1 digit long, and they can have an optional exponent part. Here’s an example:

iex> 1.0e-10
1.0e-10

Keep in mind that float number precision can be a little imprecise due to the way they are stored in memory.

Remember that in Elixir, as in other languages, when you perform arithmetic with both integers and floats, the result will be a float to maintain precision.

iex> 2 * 3.5
7.0

The use of floor division (div) and modulo operation (rem) can return integer values:

iex> div(10, 3)
3

iex> rem(10, 3)
1

String

Overview

In Elixir, strings are binary sequences, encoded in UTF-8. This encoding enables strings to handle any Unicode character, which is a significant advantage when developing international applications.

Elixir strings are defined using double quotes ("). Here’s an example:

iex> "Hello, World!"
"Hello, World!"

Concatenation

In Elixir, you can concatenate strings using the <> operator:

iex> "Hello, " <> "World!"
"Hello, World!"

String Interpolation

Elixir provides a powerful feature called string interpolation. It allows you to embed expressions, not limited to just strings but also other data types, directly into a string. The embedded expressions are evaluated and their results are inserted into the original string at the corresponding positions. String interpolation is accomplished using the #{} syntax.

Apart from strings, Elixir’s string interpolation also works with other data types like integers, floats, atoms, and even lists or tuples of integers or characters. When these data types are interpolated, they are automatically converted to string format within the larger string.

Below are examples that demonstrate string interpolation with various data types:

iex> first_name = "Stefan"
iex> greeting = "Hello #{first_name}!" (1)
"Hello Stefan!"

iex> counter = 23
iex> "Count: #{counter}"
"Count: 23"
1Here, we’ve used the #{} syntax to insert the value of the first_name variable into the string.

This string interpolation feature provides a convenient way to incorporate dynamic content into strings, enhancing the flexibility of text manipulation in Elixir.

Multiline Strings

Elixir also supports multiline strings. You can define a multiline string by wrapping the text in triple double quotes ("""):

iex> """
...> Hello,
...> World!
...> """
"Hello,\nWorld!\n"

Notice that Elixir automatically inserts newline characters (\n) where the line breaks occur.

Escape Characters

In certain situations, we might want to include special characters in our strings that can’t be typed directly. For instance, we might want to include newline to split a string across multiple lines.

These special characters can be represented using escape sequences, which are initiated by a backslash (\). Here are some common escape sequences:

  • \" - Double quote

  • \' - Single quote

  • \\ - Backslash

  • \n - Newline

  • \t - Tab

Here are some examples of using escape sequences:

iex> "Hello, \"World!\"" (1)
"Hello, \"World!\""
iex> "Line 1\nLine 2" (2)
"Line 1\nLine 2"
iex> "Column 1\tColumn 2" (3)
"Column 1\tColumn 2"
1The \" escape sequence allows us to include double quotes within a string.
2The \n escape sequence represents a newline, which splits a string across multiple lines.
3The \t escape sequence represents a tab, which creates some horizontal space in the string.

String Functions

Elixir provides a String module that offers a comprehensive set of functions for working with strings. With these functions, you can perform a variety of operations such as changing case, trimming whitespace, splitting and joining strings, repeating strings, replacing substrings, and much more.

For example, you can use the upcase function to convert a string to uppercase:

iex> String.upcase("hello")
"HELLO"

You can use the downcase function to convert a string to lowercase:

iex> String.downcase("HELLO")
"hello"

The trim function allows you to remove leading and trailing whitespace:

iex> String.trim("  hello  ")
"hello"

With the split function, you can divide a string into a list of substrings:

iex> String.split("Hello, World!", ", ")
["Hello", "World!"]

BTW: There is no join function in the String module. But you can use Enum.join/2 for that:

iex> Enum.join(["Hello", "World!"], ", ")
"Hello, World!"

The replace function allows you to substitute a specific pattern in a string with another string:

iex> String.replace("Hello, World!", "World", "Elixir")
"Hello, Elixir!"

These are just a few examples of the many functions available in the String module. You can find the full list of functions, along with their descriptions and examples, in the official Elixir documentation for the String module at hexdocs.pm/elixir/String.html.

Atom

Atoms in Elixir are constants that are represented by their name. They’re similar to symbols in other languages and start with a :.

They are extensively used to label or categorize values. For example, when a function might fail, it often returns a tuple tagged with an atom such as {:ok, value} or {:error, message}.

iex> :red
:red
iex> :blue
:blue
iex> is_atom(:blue) (1)
true
1The function is_atom() checks whether a value is an atom.

While atoms can be written in snake_case or CamelCase, snake_case is commonly used within the Elixir community. Ensure your atoms are descriptive and indicative of their purpose for code readability.

It’s worth noting that while atoms are handy, they aren’t garbage collected and consume system memory until the system is shut down, so they should be used sparingly. Do not dynamically create atoms from user input or untrusted data, as this can exhaust your system’s available memory and cause it to crash. It is unlikely that you run into this problem but not impossilbe.[2]

Boolean

Booleans are a data type in Elixir used to represent truthiness and falsiness. They come in two flavors: true and false.

Interestingly, in Elixir, booleans are represented as atoms under the hood. Specifically, true and false are equivalent to the atoms :true and :false, respectively. This means you can use :true and :false as if they were true and false. But please don’t. It’s generally better to use true and false when dealing with booleans, as it makes the code clearer and easier to understand.

Tuple

Tuples in Elixir are a collection of elements enclosed in curly braces {}. They can hold multiple elements of different types. Tuples are stored contiguously in memory, making data access operations quick. However, modifications (like inserting or deleting elements) can be slow because they require creating a new tuple to preserve immutability.

Tuples are like a fast train with assigned seats. You can quickly find your seat (element), no matter where it is. But if you want to add or remove passengers (modify the tuple), it’s a big deal - you pretty much need to start a new train (create a new tuple). So, tuples are great when you just want to look at your data and don’t plan to change it much.

Here’s how tuples are represented:

iex> {1, 2, 3} (1)
{1, 2, 3}
iex> {:ok, "test"} (2)
{:ok, "test"}
iex> {true, :apple, 234, "house", 3.14} (3)
{true, :apple, 234, "house", 3.14}
1A tuple containing three integers.
2A tuple with an atom representing status and a string — an often used construct in Elixir.
3A tuple containing different data types.

You can quickly access an element of a tuple by using the elem/2 function:

iex> result = {:ok, "Lorem ipsum"}
{:ok, "Lorem ipsum"}
iex> elem(result, 1) (1)
"Lorem ipsum"
iex> elem(result, 0) (2)
:ok
1The elem/2 function provides quick access to tuple elements.
2The index starts from 0 for the first element.

Tuple Functions

Elixir’s Tuple module includes various functions for manipulating tuples, such as appending or deleting elements, and converting tuples to lists. Here are some examples:

iex> results = {:ok, "Lorem ipsum"}
{:ok, "Lorem ipsum"}
iex> b = Tuple.append(results, "Test")
{:ok, "Lorem ipsum", "Test"}
iex> c = Tuple.delete_at(b, 1)
{:ok, "Test"}
iex> d = Tuple.insert_at(b, 1, "ipsum")
{:ok, "ipsum", "Lorem ipsum", "Test"}
iex> new_list = Tuple.to_list(d)
[:ok, "ipsum", "Lorem ipsum", "Test"]
iex> tuple_size(d)
4

List

On the other hand, lists, enclosed in brackets [], are implemented as linked lists, storing each element’s value and a reference to the next element. This structure makes adding elements to the start of the list fast. However, accessing individual elements or determining the list’s length is a linear operation, meaning it can take longer as the list size grows.

Lists are like a chain of people holding hands. Adding a new person at the front of the chain (adding an element to the start of the list) is easy. But if you’re looking for someone specific (accessing a particular element), you have to start at one end of the chain and check each person until you find them. So, lists are excellent when you want to keep adding new elements, but not so great if you frequently need to find a specific element.

Here’s how you can work with lists:

iex> [1, 2, 3, 4]
[1, 2, 3, 4]
iex> ["a", "b", "c"]
["a", "b", "c"]
iex> [1, "b", true, false, :blue, "house"]
[1, "b", true, false, :blue, "house"]

List concatenation and subtraction can be done using the ++ and -- operators:

iex> [1, 2] ++ [2, 4] (1)
[1, 2, 2, 4]
iex> [1, 2] ++ [1] (2)
[1, 2, 1]
iex> [1, "a", 2, false, true] -- ["a", 2] (3)
[1, false, true]
1Appends two lists.
2Adds an element to the list.
3Subtracts elements from a list.

Working with Lists: Head, Tail, and Other Operations

Elixir offers several built-in functions to operate on lists such as getting the first element (head) and the remaining elements (tail) using hd/1 and tl/1 functions. Also, functions like length/1 provide the list’s size, and various functions in the Enum and List modules assist in processing and manipulating lists.

Here are some examples:

iex> shopping_list = ["apple", "orange", "banana", "pineapple"]
["apple", "orange", "banana", "pineapple"]
iex> hd(shopping_list)
"apple"
iex> tl(shopping_list)
["orange", "banana", "pineapple"]
iex> length(shopping_list)
4
iex> numbers = [1, 5, 3, 7, 2, 3, 9, 5, 3]
[1, 5, 3, 7, 2, 3, 9, 5, 3]
iex> Enum.max(numbers)
9
iex> Enum.sort(numbers)
[1, 2, 3, 3, 3, 5, 5, 7, 9]
iex> List.last(shopping_list)
"pineapple"
No need to stress over choosing between lists and tuples early on. As you continue your journey through this book, you’ll develop an intuitive understanding of when to use which based on the specific problem at hand.

Keyword List

Keyword lists serve as a staple data structure in Elixir, taking the form of lists of tuples which act as key-value pairs, with atoms acting as keys.

Creating Keyword Lists

Typically in Elixir, the most common method to create a keyword list involves using a [key: value] syntax:

iex> user = [name: "joe", age: 23] (1)
[name: "joe", age: 23]
1This syntax offers an intuitive way to create a keyword list, with each atom (e.g., :name, :age) serving as the key.

You can access the value associated with a key simply:

iex> user[:name] (1)
"joe"
1Use the key, preceded by a colon and within brackets appended to the list name, to retrieve the associated value.

Keyword lists frequently appear in Phoenix applications, particularly as the final argument in the render/3 function:

render(conn, "show.html", message: "Hello", name: "Mary") (1)
1In this line, [message: "Hello", name: "Mary"] represents a keyword list. Note that the enclosing brackets are optional in this context.
Alternative Creation Method

Alternatively, although less commonly used, you can create a keyword list by constructing a list of 2-item tuples, with the first item of each tuple being an atom:

iex> user = [{:name, "joe"}, {:age, 23}] (1)
[name: "joe", age: 23]
1This list of tuples serves as another representation of a keyword list, equivalent to the more common [key: value] syntax mentioned earlier.

Manipulating Keyword Lists

Keyword lists can be manipulated with these functions[3]:

  • Keyword.get/2: This function retrieves the value associated with a given key within a keyword list.

    iex> list = [{:a, 1}, {:b, 2}]
    [a: 1, b: 2]
    iex> value = Keyword.get(list, :a)
    1
    iex> IO.puts(value)
    1
    :ok
  • Keyword.put/3: This function is used to either add a new key-value pair to a keyword list or update an existing one.

    iex> list = [{:a, 1}, {:b, 2}]
    [a: 1, b: 2]
    iex> updated_list = Keyword.put(list, :a, 3)
    [a: 3, b: 2]
    iex> IO.inspect(updated_list)
    [a: 3, b: 2]
    [a: 3, b: 2]
  • Keyword.delete/2: This function removes a key-value pair from a keyword list, given its key.

    iex> list = [{:a, 1}, {:b, 2}]
    [a: 1, b: 2]
    iex> updated_list = Keyword.delete(list, :a)
    [b: 2]
    iex> IO.inspect(updated_list)
    [b: 2]
    [b: 2]
  • Keyword.merge/2: This function merges two keyword lists into one. In case of duplicate keys, values from the second list overwrite those from the first.

    iex> list1 = [{:a, 1}, {:b, 2}]
    [a: 1, b: 2]
    iex> list2 = [{:b, 3}, {:c, 4}]
    [b: 3, c: 4]
    iex> merged_list = Keyword.merge(list1, list2)
    [a: 1, b: 3, c: 4]
    iex> IO.inspect(merged_list)
    [a: 1, b: 3, c: 4]
    [a: 1, b: 3, c: 4]

Duplication of Keys

Be aware that keyword lists allow duplication of keys, and this aspect affects how they are manipulated or accessed. For example:

iex> new_user = [name: "fred"] ++ user
[name: "fred", name: "joe", age: 23]
iex> new_user[:name] (1)
"fred"
1If duplicate keys are present in a keyword list, a lookup operation retrieves the first occurrence.

Map

Maps are data structures that store key-value pairs. So they are similar to keyword lists, but with some important differences:

  1. Performance

    Maps are faster at finding values for given keys, especially when the map gets large. If you have a big bunch of key-value pairs and need to frequently lookup values, a map is more efficient. Keyword lists can be slower when they grow bigger because they have to search through the list one item at a time.

  2. Key Uniqueness

    In a map, each key is unique. If you try to add an entry with a key that already exists, it will just update the existing entry. This is useful when you want to ensure there are no duplicates. With keyword lists, you can have the same key multiple times.

  3. No Key Ordering

    Keyword lists keep the order of the elements as you added them, which can be useful in certain situations. Maps, on the other hand, don’t keep track of the insertion order.

  4. Any Key Type

    Maps can have keys of any type, while keyword lists usually have atom keys. This gives maps more flexibility if you need to use different types as keys.

Maps are created using the %{} syntax.

iex(1)> product_prices = %{"Apple" => 0.5, "Orange" => 0.7} (1)
%{"Apple" => 0.5, "Orange" => 0.7}
iex(2)> product_prices["Orange"] (2)
0.7
iex(3)> product_prices["Banana"] (3)
nil
iex(4)> product_prices = %{"Apple" => 0.5, "Apple" => 1, "Orange" => 0.7}
warning: key "Apple" will be overridden in map
  iex:4

%{"Apple" => 1, "Orange" => 0.7} (4)
1A new map is created and bound to the variable product_prices.
2Value retrieval is straightforward: append the key to the map name within brackets.
3If the given key doesn’t exist, it returns nil.
4Unlike keyword lists, maps disallow duplicate keys.

Maps are flexible, allowing any data type to serve as both keys and values:

iex> %{"one" => 1, "two" => "abc", 3 => 7, true => "asdf"}
%{3 => 7, true => "asdf", "one" => 1, "two" => "abc"}
Each key must be unique within a map. If there are duplicates, the last one overwrites the previous values.

Atom Key

Maps support atom keys, enabling some handy features:

iex> product_prices = %{apple: 0.5, orange: 0.7} (1)
%{apple: 0.5, orange: 0.7}
iex> product_prices.apple (2)
0.5
iex> product_prices.banana (3)
** (KeyError) key :banana not found in: %{apple: 0.5, orange: 0.7}
1This syntax makes reading and typing easier when using atoms as keys.
2Atom keys allow the use of the dot operator (.) to access their values.
3If an attempt is made to access a nonexistent key with the dot operator, an error is thrown.

Sure, let’s break it down a little bit more and explain each part in a simpler way.

Map Functions

Elixir’s Map module is equipped with various functions that help to perform different operations on maps.[4] Let’s explore the functions I use the most.

Creating a Map

Create a map named product_prices that stores the prices of various products.

iex> product_prices = %{apple: 0.5, orange: 0.7, coconut: 1}
%{apple: 0.5, coconut: 1, orange: 0.7}

Here, we have three items - apple, orange, and coconut - each with their respective prices.

Converting a Map to a List

The Map.to_list/1 function allows us to convert a map into a keyword list.

iex> Map.to_list(product_prices)
[apple: 0.5, coconut: 1, orange: 0.7]

We can see that our map has now been transformed into a keyword list.

Retrieving All Values from a Map

To fetch all the values from a map (in our case, the product prices), we can utilize the Map.values/1 function.

iex> Map.values(product_prices)
[0.5, 1, 0.7]

This gives us the prices for all our products.

Retrieving All Keys from a Map

To fetch all the keys from a map, we can utilize the Map.keys/1 function.

iex> Map.keys(product_prices)
[:apple, :orange, :coconut]
Splitting a Map

We can split a map into two new maps based on a provided list of keys using the Map.split/2 function.

iex> Map.split(product_prices, [:orange, :apple])
{%{apple: 0.5, orange: 0.7}, %{coconut: 1}}

Our original map is divided into two maps: one containing apple and orange, and the other containing coconut.

Removing a Key-Value Pair from a Map

The Map.delete/2 function can be used when we need to remove a specific key-value pair from our map.

iex> a = Map.delete(product_prices, :orange)
%{apple: 0.5, coconut: 1}

A new map a is created where the key-value pair for orange is removed.

Removing Multiple Key-Value Pairs from a Map

For removing multiple key-value pairs, we can use the Map.drop/2 function.

iex> b = Map.drop(product_prices, [:apple, :orange])
%{coconut: 1}

We have removed apple and orange, leaving only coconut in the new map b.

Merging Two Maps

The Map.merge/2 function enables us to combine two maps.

iex> additional_prices = %{banana: 0.4, pineapple: 1.2}
%{banana: 0.4, pineapple: 1.2}
iex> Map.merge(product_prices, additional_prices)
%{apple: 0.5, banana: 0.4, coconut: 1, orange: 0.7, pineapple: 1.2}

A new map is created that contains the items and their prices from both the maps.

Adding a Key-Value Pair to a Map

The Map.put/2 function allows us to add a new key-value pair to a map.

iex> c = Map.put(product_prices, :potato, 0.2)
%{apple: 0.5, coconut: 1, orange: 0.7, potato: 0.2}

We’ve created a new map c where potato has been added to our original map with a price of 0.2.

Struct

A struct is a map variant that includes compile-time checks and default values. The defstruct construct is used to define a struct:

iex> defmodule Product do (1)
...>   defstruct name: nil, price: 0 (2)
...> end

iex> %Product{}
%Product{name: nil, price: 0}
iex> apple = %Product{name: "Apple", price: 0.5} (3)
%Product{name: "Apple", price: 0.5}
iex> apple
%Product{name: "Apple", price: 0.5}
iex> apple.price
0.5
iex> orange = %Product{name: "Orange"} (4)
%Product{name: "Orange", price: 0}
1Here we define a new struct named Product with the keys name and price.
2Default values are set for the keys.
3A new Product struct is created, setting values for all keys.
4A new Product struct is created with only the name set, leaving the price at its default value.

Structs ensure that only defined fields can be accessed:

iex> apple.description (1)
** (KeyError) key :description not found in: %Product{name: "Apple", price: 0.5}

iex> banana = %Product{name: "Banana", weight: 0.1} (2)
** (KeyError) key :weight not found
    expanding struct: Product.__struct__/1
    iex:7: (file)
iex>
1Accessing an undefined field, like description in the Product struct, will result in an error.
2Similarly, trying to set an undefined field, such as weight, while creating a new struct will also cause an error.
As structs are built on top of maps, all map functions are applicable to them.

Type Conversions in Elixir

In Elixir, type conversions are explicitly invoked by built-in functions. Here are some of the most commonly used functions to convert between different types:

  • Integer.to_string/1: This function converts an integer to a string.

    iex> Integer.to_string(42)
    "42"
  • String.to_integer/1: This function converts a string to an integer. An error is raised if the string does not represent a valid number.

    iex> String.to_integer("42")
    42
  • Float.to_string/1 and String.to_float/1: These functions convert between floating-point numbers and strings.

    iex> Float.to_string(3.14)
    "3.14"
    
    iex> String.to_float("3.14")
    3.14
  • Atom.to_string/1 and String.to_atom/1: These functions convert between atoms and strings.

    Note that String.to_atom/1 should be used sparingly because atoms are not garbage-collected, meaning that converting a large amount of unique strings into atoms can exhaust your system memory.
    iex> Atom.to_string(:elixir)
    "elixir"
    
    iex> String.to_atom("elixir")
    :elixir

    Elixir also provides Kernel.to_string/1 to convert some terms to a string. For example, lists can be converted to a string representation.

    iex> to_string([1, 2, 3])
    <<1, 2, 3>> (1)
    1This is a so called BitString.

Remember, type conversion in Elixir is explicit and must be invoked through these built-in functions. This design choice, while requiring a bit more typing, can help prevent bugs related to unexpected type conversions.

Operators

In Elixir, operators are special symbols or words that are used to perform various operations like mathematical, comparison, logical and more.

Arithmetic Operators

Arithmetic operators in Elixir allow you to perform basic mathematical calculations. These operators work with numbers and support integer as well as floating-point arithmetic.

Here are the most popular arithmetic operators:

  • Addition (+): This operator adds two numbers together.

    iex> 2 + 2
    4
  • Subtraction (-): This operator subtracts the second number from the first.

    iex> 4 - 2
    2
  • Multiplication (*): This operator multiplies two numbers.

    iex> 3 * 3
    9
  • Division (/): This operator divides the first number by the second. It always returns a float.

    iex> 10 / 2
    5.0

    If you need integer division, you can use the div/2 function:

    iex> div(10, 2)
    5

These operators follow standard mathematical precedence rules. If you want to ensure a specific order of operations, use parentheses to make your intentions clear:

iex> 2 + 2 * 3
8

iex> (2 + 2) * 3
12

Elixir’s arithmetic operators are not just for integers and floats; they can also operate on other data types, such as complex numbers and matrices, provided the appropriate libraries are installed. However, the focus here is on their use with numbers.

Comparison Operators

Comparison operators in Elixir are used to compare two values. They evaluate to either true or false. These operators play a crucial role in controlling program flow through conditions.

Here are the primary comparison operators:

  • Less Than (<): This operator returns true if the value on the left is less than the value on the right.

    iex> 2 < 3
    true
  • Less Than or Equal To (⇐): This operator returns true if the value on the left is less than or equal to the value on the right.

    iex> 2 <= 2
    true
  • Greater Than (>): This operator returns true if the value on the left is greater than the value on the right.

    iex> 3 > 2
    true
  • Greater Than or Equal To (>=): This operator returns true if the value on the left is greater than or equal to the value on the right.

    iex> 3 >= 3
    true
  • Equal To (==): This operator returns true if the value on the left is equal to the value on the right.

    iex> 2 == 2
    true
  • Not Equal To (!=): This operator returns true if the value on the left is not equal to the value on the right.

    iex> 2 != 3
    true

These operators are typically used in conditional expressions such as those found in if, unless, and cond statements.

It’s important to note that Elixir provides strict comparison operators as well (=== and !==). These are identical to == and != respectively, but additionally distinguish between integers and floats.

iex> 2 == 2.0
true
iex> 2 === 2.0
false

Boolean Operators

Boolean operators help to control logical flow within your program. They perform operations on boolean values and return a boolean result (true or false). The primary boolean operators include and, or, not, and xor.

  • and: The and operator returns true if both the operands are true. Otherwise, it returns false.

    iex> true and true
    true
    
    iex> true and false
    false
  • or: The or operator returns true if at least one of the operands is true.

    iex> false or true
    true
    
    iex> false or false
    false
  • not: The not operator returns the opposite boolean value of the operand.

    iex> not true
    false
    
    iex> not false
    true
  • xor: The xor (exclusive or) operator returns true if exactly one of the operands is true.

    iex> true xor false
    true
    
    iex> true xor true
    false

Short-Circuit Operators

In addition to the boolean operators and, or, and not, Elixir provides &&, ||, and ! as equivalent short-circuit operators. Short-circuit evaluation, also known as minimal evaluation, is a method of evaluation in which the second argument is executed or evaluated only if the first argument does not suffice to determine the value of the expression.

However, these operators handle non-boolean values differently than their counterparts, which is important to understand to avoid unexpected behavior in your Elixir programs.

  • && operator: This operator returns the first value if it is falsy (either false or nil). Otherwise, it returns the second value. This is why it only evaluates the second argument if the first one is truthy.

    iex> nil && true
    nil

    In the above example, nil && true returns nil because nil is a falsy value.

  • || operator: This operator returns the first value if it is truthy. Otherwise, it returns the second value. It only evaluates the second argument if the first one is falsy.

    iex> true || "Hello"
    true

    In this example, true || "Hello" returns true because true is a truthy value.

  • ! operator: This operator negates the truthiness of the value. It returns false for all truthy values and true for all falsy values.

    iex> !1
    false

    Here, !1 returns false because 1 is considered a truthy value in Elixir.

    === String Concatenation Operator <>

In Elixir, the <> operator takes two strings and merges them together, forming a new string. This process is commonly known as "string concatenation." Here’s a simple example:

iex> "Hello" <> " world"
"Hello world"

In the above example, "Hello" and " world" are two separate strings. The <> operator combines them into one string: "Hello world".

In practice, the <> operator is extensively used when there’s a need to dynamically construct a string. It allows parts of the string to be variables that can change depending on the program’s context:

iex> greeting = "Hello"
"Hello"
iex> name = "Alice"
"Alice"
iex> greeting <> ", " <> name
"Hello, Alice"

In the example above, greeting and name are variables holding different string values. Using the <> operator, we can concatenate these variables with another string (", ") to create the desired output.

It’s important to note that the <> operator only works with strings. Attempting to concatenate a non-string data type without first converting it to a string will result in an error:

iex> "The answer is " <> 42
** (ArgumentError) "argument error"

To avoid such errors, ensure all operands of the <> operator are strings. For example, you can use the Integer.to_string/1 function to convert an integer to a string:

iex> "The answer is " <> Integer.to_string(42)
"The answer is 42"

Interpolation Operator #{}

The interpolation operator in Elixir, represented as #{}, is a powerful tool used for inserting values within a string. It allows for dynamic expression of values within a string without the need for explicit concatenation. Here’s a simple example:

iex> name = "Alice"
"Alice"
iex> "Hello, #{name}"
"Hello, Alice"

In the example above, the variable name is interpolated into the string. The resulting string is "Hello, Alice".

String interpolation in Elixir can handle more complex expressions, not just variables. This includes arithmetic operations, function calls, and more:

iex> number = 5
5
iex> "The square of #{number} is #{number * number}"
"The square of 5 is 25"

In the above code, the expressions inside the interpolation operator #{} are evaluated, and their results are automatically converted into strings and inserted in the appropriate place.

Another powerful aspect of string interpolation in Elixir is that it’s not restricted to strings. It can handle any data type that can be meaningfully converted into a string representation, including integers, floats, atoms, lists, and even tuples:

iex> tuple = {1, 2, 3}
{1, 2, 3}
iex> "The tuple is #{tuple}"
"The tuple is {1, 2, 3}"

In the above example, the tuple is automatically converted to its string representation and inserted into the string.

Remember, the expressions inside the interpolation operator #{} must be valid Elixir expressions.

The #{} operator itself can’t be used outside a string, as it’s a part of the string syntax, not a standalone operator.

The Pipe Operator (|>)

The pipe operator |> is an effective tool in enhancing the readability of your code. Referred to as syntactic sugar, it directs the output from the expression to its left as the first argument into the function on its right. It thus allows for a clean and streamlined way to chain multiple functions together.

It is easier than it sounds. The following code examples explain it.

Consider a case where you wish to reverse a string with String.reverse/1 and subsequently capitalize it using String.capitalize/1. Traditionally, you might go about it as follows:

iex> String.reverse("house") (1)
"esuoh"
iex> String.capitalize("esuoh") (2)
"Esuoh"
iex> String.capitalize(String.reverse("house")) (3)
"Esuoh"
1String.reverse/1 function reverses the string.
2String.capitalize/1 function capitalizes the first letter of a string.
3Both functions are integrated to first reverse and then capitalize the string.

Although String.capitalize(String.reverse("house")) is technically correct, it can be a bit difficult to read. This is where the pipe operator |> comes in handy:

iex> "house" |> String.reverse() |> String.capitalize() (1)
"Esuoh"
1The pipe operator |> passes the result of the first function as the first parameter to the subsequent function.

Moreover, the pipe operator can be seamlessly chained for multiple operations:

iex> "house" |> String.reverse() |> String.capitalize() |> String.slice(0, 3)
"Esu"

Employing the pipe operator, the code becomes more legible, easier to understand, and more maintainable. The benefits of this operator are particularly noticeable in multi-line source code where each transformation is clearly outlined:

example =
  "house"
  |> String.reverse()
  |> String.capitalize()
  |> String.slice(0, 3)

This presentation enhances clarity and readability of the code, allowing for better understanding and maintenance.

The Match Operator = (Pattern Matching)

In various programming languages, the equals sign (=) signifies assignment. For example, x = 5 generally reads as "Let x be equal to 5." Elixir, however, assigns a slightly different role to the equals sign.

In Elixir, = acts as the _match operator. Trying to match the right side of the expression with its left side. When we say x = 5 in Elixir, we are essentially telling the language, "Match x to the value 5." If x is unbound or has no assigned value, Elixir will bind x to 5, making it behave similarly to an assignment operator. But if x already holds a value, Elixir will attempt to rebind it. Older versions of Elixir did throw an error in this case, but this behavior was changed for a better user experience.

iex> x = 5
5

Elixir’s pattern matching becomes even more robust with more complex data types like tuples or lists:

iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
iex> c
42

In the above snippet, Elixir matches variables a, b, and c to their respective values in the right-side tuple. Thus a equals :hello, b equals "world", and c equals 42.

While it might seem similar to destructuring in JavaScript or other languages, remember that Elixir aims to match instead of simply assigning. If no match is found, Elixir throws an error:

iex> {d, e, f} = {:hi, "there", 23}
{:hi, "there", 23}
iex> {d, e} = {:hi, "there", 23}
** (MatchError) no match of right hand side value: {:hi, "there", 23}

In the second command, a two-element pattern cannot match a three-element tuple, thus resulting in a MatchError.

To summarize, pattern matching in Elixir verifies whether a certain pattern matches your data. If it does, Elixir assigns values to variables based on that pattern.

Pattern Matching is an incredibly powerful concept in Elixir, used in myriad ways. This is merely an introduction—we will explore more examples throughout this book.

Functions

Pattern matching is pervasive in Elixir. It is used with functions too:

iex> defmodule Area do
...>   def circle(:exact, radius) do (1)
...>     3.14159265359 * radius * radius
...>   end
...>
...>   def circle(:normal, radius) do (2)
...>     3.14 * radius * radius
...>   end
...>
...>   def circle(radius) do (3)
...>     circle(:normal, radius)
...>   end
...> end

iex> Area.circle(:exact, 4)
50.26548245744
iex> Area.circle(:normal, 4)
50.24
iex> Area.circle(4)
50.24
1We define a circle/2 function which matches if the first argument is the atom :exact.
2We define a circle/2 function which matches if the first argument is the atom :normal.
3We define a circle/1 function which calls the circle/2 function with the :normal argument.

Functions with Guards

Guards add extra layers to pattern matching with functions. Full details can be found at https://hexdocs.pm/elixir/guards.html. Let’s look at a few examples. Guards start with when:

iex> defmodule Law do
...>   def can_vote?(age) when is_integer(age) and age > 17 do (1)
...>     true
...>   end
...>
...>   def can_vote?(age) when is_integer(age) do (2)
...>     false
...>   end
...>
...>   def can_vote?(_age) do (3)
...>     raise ArgumentError, "age should be an integer"
...>   end
...> end

iex> Law.can_vote?(18)
true
iex> Law.can_vote?(16)
false
iex> Law.can_vote?("18")
** (ArgumentError) age should be an integer
1We define a can_vote?/1 function with a guard clause that checks whether the age is an integer and greater than 17.
2We define a can_vote?/1 function with a guard clause that checks whether the age is an integer.
3We define a can_vote?/1 function to handle other cases.

Pattern Matching With Various Data Structures

Pattern matching extends to various data structures in Elixir, including lists, maps, strings, and even function clauses. Let’s see how this works.

Lists

Elixir provides a unique syntax for pattern matching the head and tail of a list. Let’s consider the following examples:

iex> shopping_list = ["apple", "orange", "banana", "pineapple"] (1)
["apple", "orange", "banana", "pineapple"]
iex> [head | tail] = shopping_list (2)
["apple", "orange", "banana", "pineapple"]
iex> head
"apple"
iex> tail
["orange", "banana", "pineapple"]
iex> [a | b] = tail (3)
["orange", "banana", "pineapple"]
iex> a
"orange"
iex> b
["banana", "pineapple"]
iex> [first_product, second_product | tail] = shopping_list (4)
["apple", "orange", "banana", "pineapple"]
iex> first_product
"apple"
iex> second_product
"orange"
iex> tail
["banana", "pineapple"]
iex> [first_product | [second_product | tail]] = shopping_list (5)
["apple", "orange", "banana", "pineapple"]
1We match a list to the variable shopping_list.
2[head | tail] is the special syntax to match a head and tail of a given list.
3Here we match the head a and the tail b with tail.
4This is slightly more complex. We match the first and second product followed by a tail.
5This alternative syntax yields the same result but follows different logic. Choose the one you prefer.

If we know that a list has a specific number of elements, we can match it directly:

iex> shopping_list = ["apple", "orange", "banana", "pineapple"]
["apple", "orange", "banana", "pineapple"]
iex> [a, b, c, d] = shopping_list
["apple", "orange", "banana", "pineapple"]
iex> a
"apple"
iex> b
"orange"
iex> [e, f, g] = shopping_list (1)
** (MatchError) no match of right hand side value: ["apple", "orange", "banana", "pineapple"]
1Just checking. You get an MatchError if Elixir can’t match both sides.

Keyword Lists

In Elixir, pattern matching on keyword lists is no longer recommended due to strict ordering and count requirements introduced in version 1.15. While pattern matching was once possible, recent versions of Elixir make it unreliable. Keyword lists are often used for optional arguments where keys may be omitted, making pattern matching impractical.

Instead of pattern matching, access values directly by key using the [] syntax:

iex> list = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]

iex> list[:a]
1

iex> list[:c]
3

Matching Inside Functions

Pattern matching with keyword lists is often used in function heads. Consider a system where you want to provide different messages to users based on their role. You could achieve this with pattern matching on keyword lists:

defmodule User do
  def greet(name, opts \\ []) (1)

  def greet(name, [role: "admin"]) do
    "Welcome, #{name}. You have admin privileges."
  end

  def greet(name, [role: "moderator"]) do
    "Welcome, #{name}. You can moderate content."
  end

  def greet(name, []) do
    "Welcome, #{name}."
  end
end

IO.puts User.greet("Alice") # Outputs: "Welcome, Alice."

IO.puts User.greet("Bob", role: "admin") # Outputs: "Welcome, Bob. You have admin privileges."

IO.puts User.greet("Carol", role: "moderator") # Outputs: "Welcome, Carol. You can moderate content."
1We define a greet/2 function header with a default value for the second argument. The default value is an empty list [].

In this example, we define different greetings based on user roles. When calling the greet function, we can optionally provide a role.

Maps

Matching a map in Elixir differs slightly from tuples or lists. We can match specific values we’re interested in:

iex> product_prices = %{apple: 0.5, orange: 0.7, pineapple: 1}
%{apple: 0.5, orange: 0.7, pineapple: 1}
iex> %{orange: price} = product_prices (1)
%{apple: 0.5, orange: 0.7, pineapple: 1}
iex> price
0.7
iex> %{orange: price1, apple: price2} = product_prices (2)
%{apple: 0.5, orange: 0.7, pineapple: 1}
iex> price1
0.7
iex> price2
0.5
1Here we match just one value.
2We can match multiple values. It’s not necessary to match the entire map.

Strings

Pattern matching with strings is best illustrated with a code snippet:

iex> user = "Stefan Wintermeyer"
"Stefan Wintermeyer"
iex> "Stefan " <> last_name = user
"Stefan Wintermeyer"
iex> last_name
"Wintermeyer"
The left side of a <> operator in a match should always be a string. Otherwise, Elixir can’t determine its size.

Wildcards

Sometimes you want to pattern match something but you don’t care about the value. By using the _ wildcard, either standalone or as a prefix to a variable name, you signal to Elixir that there’s no requirement for a binding to a particular variable. Here are two examples:

iex(1)> cart = {"apple", "orange", "banana"}
{"apple", "orange", "banana"}
iex(2)> {first, _, _} = cart (1)
{"apple", "orange", "banana"}
iex(3)> IO.puts(first)
"apple"

iex(4)> cart2 = ["apple", "orange", "banana", "pineapple"]
["apple", "orange", "banana", "pineapple"]
iex(5)> [head | _tail] = cart2 (2)
["apple", "orange", "banana", "pineapple"]
iex(6)> IO.puts(head)
"apple"
1We use wildcards _ to ignore "orange" and "banana" in the cart tuple while pattern matching the first item to first.
2With the list cart2, we pattern match the first item to head, ignoring the rest of the list by prefixing _ to tail.
Using tail instead of just increases the readability of the code.

The Range Operator

The range operator (..) in Elixir introduces a convenient way of defining sequences of successive integers. This section will explore the range operator, demonstrating its various uses from simple range definitions to utilization in functions for processing sequences.

Understanding the Range Operator ..

In Elixir, a range is created by two integers separated by the .. operator. This sequence includes both the start and end points. For example, 1..5 creates a range of integers from 1 to 5 inclusive. 5..1 does the same but in reverse order.

Ranges in Elixir are considered as enumerables, which means they can be used with the Enum module to iterate over the sequence of numbers. This capability makes the range operator a versatile tool in various situations involving sequences of numbers.

iex> 1..5
1..5
iex> Enum.to_list(1..5)
[1, 2, 3, 4, 5]

In the code above, the first command creates a range from 1 to 5. The second command converts the range into a list using the Enum.to_list/1 function.

Range Operator in Functions

The range operator can also be used in combination with functions:

iex> Enum.map(1..5, fn x -> x * x end)
[1, 4, 9, 16, 25]

In this example, the Enum.map function is used to square each number in the range 1..5, resulting in a new list [1, 4, 9, 16, 25].

Step in Ranges

Elixir also allows you to define the step (increment) between successive numbers in a range using the // operator:

iex> Enum.to_list(1..11//3)
[1, 4, 7, 10]

In this example, 1..11//3 creates a range from 1 to 11 with a step of 3, resulting in the list [1, 4, 7, 10].

Capture Operator &

The Capture operator, denoted as &, is a unique feature in Elixir that enables the creation of anonymous functions, often in a more succinct and readable way than the traditional fn → end syntax.

The Capture operator is often used to create quick, inline functions. Here’s a simple example:

iex> add = &(&1 + &2)
#Function<12.128620087/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3

In the above example, &(&1 + &2) creates an anonymous function that adds two arguments together. The placeholders &1 and &2 refer to the first and second arguments, respectively. The function is then assigned to the variable add, and it can be invoked with add.(1, 2).

The Capture operator isn’t just for simple functions. It can be used with more complex expressions and even function calls:

iex> double = &(&1 * 2)
#Function<6.128620087/1 in :erl_eval.expr/5>
iex> double.(10)
20

In the above example, &(&1 * 2) creates an anonymous function that doubles its argument.

You can also use the Capture operator to reference named functions from modules. For example, to reference the length function from the List module:

iex> len = &length/1
&:erlang.length/1
iex> len.([1, 2, 3, 4, 5])
5

In the example above, &length/1 captures the length function from the which takes one argument (/1). This function is then assigned to the variable len.

Cons Operator | in Elixir

The Cons operator, represented by the pipe character (|), is a core tool in Elixir used to construct and manipulate lists.

Understanding the Cons Operator |

In Elixir, lists are fundamentally built as singly linked lists. That means each element in the list holds its value and also the remainder of the list. These individual pieces are referred to as the "head" and the "tail" of the list. The Cons operator is used to combine a head and a tail into a list.

Here is a simple example:

iex> list = [1 | [2 | [3 | []]]]
[1, 2, 3]

In this example, the expression [1 | [2 | [3 | []]]] constructs a list with the elements 1, 2, and 3. The last list in the chain is an empty list [].

Using the Cons Operator |

One common usage of the Cons operator is in pattern matching to destructure a list into its head and tail:

iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]

In the example above, [head | tail] = [1, 2, 3] splits the list [1, 2, 3] into the head (the first element, 1) and the tail (the remainder of the list, [2, 3]).

This operator is particularly useful when working with recursive functions that need to operate on each element of a list.

Caution when Using the Cons Operator |

While the Cons operator can be used to construct lists, it should be noted that the resulting data structure is only properly recognized as a list if the tail is a list or an empty list. For example:

iex> ["Hello" | "World"]
["Hello"| "World"]

In this case, since "World" is not a list, Elixir does not treat the entire structure as a regular list.

Logical Expressions

The boolean type in Elixir can be either true or false. You can use logical operators like and, or, and not to manipulate these boolean values:

iex> true and true
true
iex> true or false
true
iex> not true
false

Elixir’s and, or, and not operators strictly work with boolean values. But there’s more! Elixir also provides && (and), || (or), and ! (not) operators that can handle truthy and falsy values, giving them a bit of flexibility. In Elixir, every value is considered truthy except for false and nil, which are falsy.

To clarify:

  • && (and) returns the first falsy value or the last value if all are truthy.

  • || (or) returns the first truthy value or the last value if all are falsy.

  • ! (not) returns false if its argument is truthy and true if it’s falsy.

Let’s consider a few examples:

iex> true && :hello
:hello
iex> false || "world"
"world"
iex> !nil
true

In the first example, :hello is returned because true && :hello evaluates to the last truthy value, which is :hello. In the second example, "world" is returned because false || "world" evaluates to the first truthy value, which is "world". In the final example, !nil gives true because nil is a falsy value and ! flips it to true.

Enumerables

An enumerable is any data structure that can be traversed or iterated over. It can be lists like [1, 2, 3], maps like %{a: 1, b: 2} and ranges like 1..3. All these structures can be processed using the functions provided by the Enum module.

Within Elixir, there are two main modules for processing enumerables: Enum and Stream.

The Enum Module

Introduction

The Enum module contains functions for mapping, filtering, grouping, sorting, reducing, and other operations. They are the building blocks for manipulating and transforming data collections in a functional and declarative style, enhancing code readability and maintainability.

Consider this example of using Enum to multiply each element in a list by 2:

list = [1, 2, 3, 4]
Enum.map(list, fn x -> x * 2 end)
# => [2, 4, 6, 8]

The Enum.map function takes two arguments: the enumerable (list in this case) and a transformation function for each element.

For further enhancement, Elixir’s pipe operator (|>) can be used with Enum functions for cleaner and more readable code. Here’s an example:

list = [1, 2, 3, 4]
list
  |> Enum.map(fn x -> x * 3 end)
  |> Enum.filter(fn x -> rem(x, 2) == 0 end)
# => [6, 12]

This statement takes a list, multiplies each element by 3 using Enum.map, and then filters out the odd numbers using Enum.filter. The use of the pipe operator makes the code flow naturally and easier to read.

You can also use the &1 shorthand for anonymous functions (see Capture Operator) to increase code readability. Here’s the previous example using the shorthand:

list = [1, 2, 3, 4]
list
  |> Enum.map(&(&1 * 3))
  |> Enum.filter(&rem(&1, 2) == 0)
# => [6, 12]
Enum functions are eager; they execute immediately and return a result. If memory usage is a concern with very large collections, consider using the Stream module for lazy computation.

Commonly Used Enum Functions

Enum offers a ton of useful functions. All are listed at the official Enum documentation. Here are some of the most commonly used functions to give you an idea of what’s available.

Enum.map/2

The Enum.map/2 function is used to transform each element in an enumerable using a provided function.

list = [1, 2, 3, 4]
Enum.map(list, fn x -> x * 2 end)
# => [2, 4, 6, 8]

The &1 shorthand can be used as follows:

list = [1, 2, 3, 4]
list |> Enum.map(&(&1 * 2))
# => [2, 4, 6, 8]

More details can be found at the official Elixir Enum.map/2 documentation.

Enum.filter/2

The Enum.filter/2 function filters out elements based on a provided function.

list = [1, nil, 2, nil, 3]
Enum.filter(list, fn x -> x != nil end)
# => [1, 2, 3]

Using the &1 shorthand:

list = [1, nil, 2, nil, 3]
list |> Enum.filter(&(&1 != nil))
# => [1, 2, 3]

More details can be found at the official Elixir Enum.filter/2 documentation.

Enum.reduce/2,3

The Enum.reduce/2,3 function reduces an enumerable to a single value.

list = [1, 2, 3, 4]
Enum.reduce(list, 0, fn x, acc -> x + acc end)
# => 10
The use of reduce/3 and it’s accumulator is similar to the fold function in other languages. It can be tricky to use.

More details can be found at the official Elixir Enum.reduce/2 documentation.

Enum.sort/1,2

The Enum.sort/1,2 function sorts the elements in an enumerable.

list = [4, 2, 3, 1]
Enum.sort(list)
# => [1, 2, 3, 4]

You can provide a comparator function:

list = [4, 2, 3, 1]
Enum.sort(list, fn a, b -> a > b end)
# => [4, 3, 2, 1]

More details can be found at the official Elixir Enum.sort/2 documentation.

Enum.at/2,3

Returns the element at the given index (zero based) or a default value.

list = [1, 2, 3, 4]
Enum.at(list, 2)
# Output: 3

More details can be found at the official Elixir Enum.at/2,3 documentation.

Enum.concat/1,2

Concatenates the collection of enumerable(s) given.

Enum.concat([[1, 2], [3, 4]])
# Output: [1, 2, 3, 4]

More details can be found at the official Elixir Enum.concat/1,2 documentation.

Enum.count/1,2

Counts the enumerable items, optionally, using the provided function.

list = [1, 2, 3, 4]
Enum.count(list)
# Output: 4

More details can be found at the official Elixir Enum.count/1,2 documentation.

Enum.find/2,3

Finds the first element for which the provided function returns a truthy value.

list = [1, 2, 3, 4]
Enum.find(list, fn x -> x > 2 end)
# Output: 3

More details can be found at the official Elixir Enum.find/2,3 documentation.

Enum.group_by/2,3

Groups all items in the enumerable by the given function.

list = [{:apple, "fruit"}, {:carrot, "vegetable"}, {:banana, "fruit"}]
Enum.group_by(list, fn {_name, type} -> type end)
# Output: %{"fruit" => [{:apple, "fruit"}, {:banana, "fruit"}], "vegetable" => [{:carrot, "vegetable"}]}

More details can be found at the official Elixir Enum.group_by/2,3 documentation.

Enum.join/1,2

Joins all the items in the enumerable into a single string.

list = ["Hello", "World"]
Enum.join(list, " ")
# Output: "Hello World"

More details can be found at the official Elixir Enum.join/1,2 documentation.

Enum.max/1

Returns the maximum value in the enumerable.

list = [1, 2, 3,

4]
Enum.max(list)
# Output: 4

More details can be found at the official Elixir Enum.max/1 documentation.

Enum.min/1

Returns the minimum value in the enumerable.

list = [1, 2, 3, 4]
Enum.min(list)
# Output: 1

More details can be found at the official Elixir Enum.min/1 documentation.

Enum.random/1

Selects a random element from the enumerable.

list = [1, 2, 3, 4]
Enum.random(list)
# Output: Random value from the list

More details can be found at the official Elixir Enum.random/1 documentation.

Enum.reject/2

Filters out the items in the enumerable for which the provided function returns a truthy value.

list = [1, 2, 3, 4]
Enum.reject(list, fn x -> x < 3 end)
# Output: [3, 4]

More details can be found at the official Elixir Enum.reject/2 documentation.

Enum.sum/1

Returns the sum of all items in the enumerable.

list = [1, 2, 3, 4]
Enum.sum(list)
# Output: 10

More details can be found at the official Elixir Enum.sum/1 documentation.

The Stream Module

Introduction

The Stream module contains functions similar to the Enum module for mapping, filtering, grouping, sorting, reducing, and other operations. However, the key difference is that Stream operations are lazy. This means they only compute results when necessary, potentially saving a lot of resources when dealing with large data sets or I/O operations.

Consider this example of using Stream to multiply each element in a list by 2:

list = [1, 2, 3, 4]
Stream.map(list, fn x -> x * 2 end)
# => #Stream<[enum: [1, 2, 3, 4], funs: [#Function<45.122072036/1 in Stream.map/2>]]>

You might notice that the output is not a list but a Stream. To retrieve the final result, you will need to convert the stream back into a list:

list = [1, 2, 3, 4]
list
|> Stream.map(fn x -> x * 2 end)
|> Enum.to_list()
# => [2, 4, 6, 8]

The Stream module can be used with Elixir’s pipe operator (|>) to create a sequence of transformations. The transformations only get executed when the stream is converted into a list or another enumerable.

This is how you would use Stream to multiply each element by 3 and then filter out odd numbers:

list = [1, 2, 3, 4]
list
  |> Stream.map(fn x -> x * 3 end)
  |> Stream.filter(fn x -> rem(x, 2) == 0 end)
  |> Enum.to_list()
# => [6, 12]

Commonly Used Stream Functions

Stream offers a lot of useful functions, similar to Enum. All are listed at the official Stream documentation. Here are some of the most commonly used functions to give you an idea of what’s available.

Stream.map/2

The Stream.map/2 function is used to transform each element in an enumerable using a provided function. The result is a new Stream that can be evaluated later.

list = [1, 2, 3, 4]
list
|> Stream.map(fn x -> x * 2 end)
|> Enum.to_list()
# => [2, 4, 6, 8]

The &1 shorthand can be used as follows:

list = [1, 2, 3, 4]
list
|> Stream.map(&(&1 * 2))
|> Enum.to_list()
# => [2, 4, 6, 8]

More details can be found at the official Elixir Stream.map/2 documentation.

Stream.filter/2

The Stream.filter/2 function filters out elements based on a provided function, resulting in a new Stream.

list = [1, nil, 2, nil, 3]
list
|> Stream.filter(fn x -> x != nil end)
|> Enum.to_list()
# => [1, 2, 3]

Using the &1 shorthand:

list = [1, nil, 2, nil, 3]
list
|> Stream.filter(&(&1 != nil))
|> Enum.to_list()
# => [1, 2, 3]

More details can be found at the official Elixir Stream.filter/2 documentation.

Stream.reduce/2,3

The Stream.reduce/2,3 function reduces an enumerable to a single value.

list = [1, 2, 3, 4]
Stream.reduce(list, 0, fn x, acc -> x + acc end)
# => 10
The use of reduce/3 and it’s accumulator is similar to the fold function in other languages. It can be tricky to use.

More details can be found at the official Elixir Stream.reduce/2 documentation.

Stream.take/2

The Stream.take/2 function generates a new stream that takes the first n items from the original stream.

list = [1, 2, 3, 4]
list
|> Stream.take(2)
|> Enum.to_list()
# => [1, 2]

More details can be found at the official Elixir Stream.take/2 documentation.

Stream.drop/2

The Stream.drop/2 function generates a new stream that drops the first n items from the original stream.

list = [1, 2, 3, 4]
list
|> Stream.drop(2)
|> Enum.to_list()
# => [3, 4]

More details can be found at the official Elixir Stream.drop/2 documentation.

Stream.concat/1,2

The Stream.concat/1,2 function generates a new stream that concatenates two streams or a stream of streams.

Stream.concat([1, 2], [3, 4])
|> Enum.to_list()
# => [1, 2, 3, 4]

More details can be found at the official link:https://hex

docs.pm/elixir/Stream.html#concat/2[Elixir Stream.concat/1,2 documentation].

Stream.cycle/1

The Stream.cycle/1 function generates an infinite stream repeating the given enumerable.

Stream.cycle([1, 2])
|> Stream.take(5)
|> Enum.to_list()
# => [1, 2, 1, 2, 1]

More details can be found at the official Elixir Stream.cycle/1 documentation.

Stream.unzip/1

The Stream.unzip/1 function generates two new streams from a stream of tuples.

list = [{1, "a"}, {2, "b"}, {3, "c"}]
{left, right} = Stream.unzip(list)
Enum.to_list(left)
# => [1, 2, 3]
Enum.to_list(right)
# => ["a", "b", "c"]

More details can be found at the official Elixir Stream.unzip/1 documentation.

Enum vs Stream

Think about Enum and Stream as two different chefs in a kitchen who are asked to prepare a large meal.

The Enum Chef (Eager Chef):

The Enum chef is eager to get the job done. He tries to cook everything at once. He gets every ingredient, every pot and pan, and starts cooking immediately. This is great if you’re not cooking a lot of food, because everything gets done fast.

But what if the meal or the number of meals is huge? Well, then the Enum chef might run into trouble. His kitchen (or our computer’s memory) might not have enough room for all the food he’s trying to cook at once. He might get overwhelmed because he’s trying to do too much at once.

The Stream Chef (Lazy Chef):

The Stream chef, on the other hand, is more laid-back. He doesn’t start cooking until it’s absolutely necessary. He prepares each dish one at a time, using only the ingredients and cookware needed for each dish. Once a dish is ready, he moves on to the next one.

If the meal is huge, the Stream chef handles it better because he only works on one dish at a time, which means he doesn’t need a lot of room in his kitchen. He’s more efficient with large meals because he can handle them piece by piece.

Comparing the Chefs:

  • Speed: The Enum chef (Eager chef) works faster when the meal is small because he cooks everything at once. But the Stream chef (Lazy chef) could be faster for large meals because he efficiently handles them one dish at a time. You could use a stopwatch to see who finishes cooking first.

  • Kitchen Space (Memory): The Stream chef (Lazy chef) uses his kitchen space more efficiently because he only prepares one dish at a time. This difference becomes obvious when they’re asked to prepare a large meal. You could look at how messy their kitchens are to see who uses space better.

So, when you’re choosing between Enum and Stream in your Elixir code, think about the size of your "meal" (your data), and pick the chef that suits your needs best.

Sigils

Sigils are another way of representing literals. A literal is a notation for representing a fixed value in source code. Sigils start with a tilde (~) character, which is followed by a letter, and then there is some content surrounded by delimiters. There are 8 different delimiters (having different delimiters means that you can choose one which reduces the need to escape characters in the content).

~s/example text/
~s|example text|
~s"example text"
~s'example text'
~s(example text)
~s[example text]
~s{example text}
~s<example text>

In the following sections, we will explore some of the most commonly used sigils in Elixir: ~s for strings, ~r for regular expressions, ~w for word lists, and those for date/time structs. It is also possible for you to create your own sigils.

The ~s and ~S Sigils

The ~s and ~S sigils in Elixir are used for creating strings.

Let’s look at some examples of using the ~s sigil:

iex> ~s(Hello, my friend!) (1)
"Hello, my friend!"
iex> ~s(He said, "I hope you are well") (2)
"He said, \"I hope you are well\""
iex> ~s/Hello (Goodbye)/ (3)
"Hello (Goodbye)"
1In this case, we use the () delimiters.
2We do not need to escape the double quotes (you will see that they are escaped in the output).
3By changing the delimiters, we do not need to escape the parentheses.

The ~S (uppercase) sigil also creates a string, but does not support interpolation:

iex> ~s(1 + 1 = #{1 + 1})
"1 + 1 = 2" (1)
iex> ~S(1 + 1 = #{1 + 1})
"1 + 1 = \#{1 + 1}" (2)
1The result of 1 + 1 is returned instead of #{1 + 1}.
2The content is returned as it is written, with no interpolation.

The ~r Sigil - Regular expressions

~r is the sigil used to represent a regular expression:

iex> regex = ~r/bcd/
~r/bcd/
iex> "abcde" =~ regex
true
iex> "efghi" =~ regex
false

As you can see, the ~r sigil allows you to easily create regular expressions in Elixir. It checks if the given string contains the regular expression pattern.

Modifiers are supported to change the behavior of the regular expressions. Two examples:

  • i: Makes the regular expression case-insensitive.

  • m: Causes ^ and $ to mark the beginning and end of each line. Use \A and \z to match the end or beginning of the string.

Here is an example of using the i modifier:

iex> regex = ~r/stef/i (1)
~r/stef/i
iex> "Stefan" =~ regex
true
1The i modifier makes the regular expression case-insensitive, so "stef" will match "Stefan".

For a complete list of modifiers, have a look at the Regex module documentation.

The ~w Sigil

The ~w sigil in Elixir helps to create a list of words without the need for quotes around each word. You start with ~w(, then put your words separated by spaces, and finish with ).

Here is an example of how it is used:

iex> words = ~w(hello world this is Elixir)
["hello", "world", "this", "is", "Elixir"]

As you can see, it turns the words separated by spaces into a list of strings.

Modifiers

For the ~w sigil, you can add a c, s, or a after the w, changing the type of the output.

  • c makes the elements character lists (charlists).

  • s makes the elements strings.

  • a makes the elements atoms.

Here are some examples:

iex> ~w(hello world this is Elixir)c
[~c"hello", ~c"world", ~c"this", ~c"is", ~c"Elixir"]

iex> ~w(hello world this is Elixir)s
["hello", "world", "this", "is", "Elixir"]

iex> ~w(hello world again)a
[:hello, :world, :again]

Also note that you can use different delimiters for your ~w sigil, not just parentheses. For example, ~w{hello world this is Elixir}, ~w/hello world this is Elixir/ and ~w|hello world this is Elixir| are all valid.

Date and Time

Elixir provides several date / time structs which all have their own sigils. These include the ~D sigil for dates, ~T for times, ~N for naive date-times, and ~U for UTC date-times.

You can find more information about timezones and DateTime at https://hexdocs.pm/elixir/DateTime.html

Date

Elixir provides a %Date{} struct that contains the fields year, month, day and calendar.

With the ~D sigil, you can create a new %Date{} struct:

iex> birthday = ~D[1973-03-23]
~D[1973-03-23]
iex> birthday.day
23
iex> birthday.month
3
iex> birthday.year
1973
iex> Date.utc_today()
~D[2020-09-23] (1)
1The return value for many of the functions in the Date module use the ~D sigil.

Time

There is a %Time{} struct that contains the fields hour, minute, second, microsecond and calendar.

With the ~T sigil, you can create a new %Time{} struct:

iex> now = ~T[09:29:00.0]
~T[09:29:00.0]
iex> now.hour
9
iex> Time.utc_now()
~T[04:57:25.658722] (1)
1The return value for many of the functions in the Time module use the ~T sigil.

NaiveDateTime

The %NaiveDateTime{} struct is a combination of %Date{} and %Time{}.

With the ~N sigil, you can create a new %NaiveDateTime{} struct:

iex> timestamp = ~N[2020-05-08 09:48:00]
~N[2020-05-08 09:48:00]

DateTime

The %DateTime{} struct adds timezone information to a %NaiveDateTime{}.

You can create a new %DateTime{} struct with the ~U sigil:

iex> timestamp = ~U[2029-05-08 09:59:03Z]
~U[2029-05-08 09:59:03Z]
iex> DateTime.utc_now()
~U[2020-09-23 04:58:22.403482Z] (1)
1The return value for many of the functions in the DateTime module use the ~U sigil.
Find more information about timezones and DateTime at https://hexdocs.pm/elixir/DateTime.html

Recursions

Recursion is a fundamental concept in functional programming. If you’re familiar with loops in other programming languages (like for or while loops), recursion serves a similar purpose in Elixir. It allows you to perform a task repeatedly, but rather than using a loop, recursion involves a function calling itself.

A recursive function is a function that solves a problem by solving smaller instances of the same problem. To prevent infinite recursion, there must be one or more base cases where the function does not call itself.

Let’s break this down with a couple of simple examples.

Recursion Example: Countdown

Let’s imagine we want to create a countdown. Here’s a simple recursive function that achieves this:

iex> defmodule Example do
...>   def countdown(1) do (1)
...>     IO.puts "1" (2)
...>   end
...>
...>   def countdown(n) when is_integer(n) and n > 1 do (3)
...>     IO.puts Integer.to_string(n) (4)
...>     countdown(n - 1) (5)
...>   end
...> end

iex> Example.countdown(4) (6)
4
3
2
1
:ok
1This is the base case: when countdown/1 is called with the argument 1, this function matches.
2We print 1 to STDOUT using IO.puts.
3If countdown/1 is called with an integer greater than 1 (we don’t want negative input here), this function matches.
4We convert the integer to a string using Integer.to_string(n) and print it.
5The function calls itself, but with n decreased by 1 - this is the recursive step.
6When we test the function, it correctly counts down from 4 to 1.

The countdown function keeps calling itself, each time reducing the initial number by one, until it reaches 1, at which point it stops, thus preventing infinite recursion.

Recursion Example: Summing a List

Here’s another example where we calculate the sum of a list of integers using recursion:

iex> defmodule Example do
...>   def sum([]) do (1)
...>     0
...>   end
...>
...>   def sum([head | tail]) do (2)
...>     head + sum(tail) (3)
...>   end
...> end

iex> Example.sum([10, 8, 12, 150]) (4)
180
1The base case: the sum of an empty list is 0.
2We pattern match a list and split it into a head (the first element) and a tail (the remaining elements).
3We add the head to the result of the recursive call, which computes the sum of the tail.
4The function correctly computes the sum of the list.

Recursion Example: Transforming a List

You can use recursion to transform every element of a list. Let’s assume we want to double the value of every element of a list:

iex> defmodule Example do
...>   def double([]) do (1)
...>     []
...>   end
...>
...>   def double([head | tail]) do
...>     [head * 2 | double(tail)] (2)
...>   end
...> end

iex> Example.double([10, 5, 999])
[20, 10, 1998]
1Base case: An empty list results in an empty list.
2We double the head and concatenate it with the result of the recursive call, which doubles the elements of the tail.

Tackling Recursion

Unless you are doing this every day, you will get to problems where you know that recursion is a good solution, but you just can’t think of a good recursion for it. That is normal. Don’t worry.

I used to say that https://www.google.com and https://stackoverflow.com were your friends. They still are but ChatGPT and Github Copilot have made our lives as programmers so much easier. Ask them. No embarrassment!

During this book, we will work with recursions. So you’ll get a better feeling for it.

Exploring mix

mix is a built-in tool in Elixir that helps to scaffold a new Elixir project with a pre-defined file and directory structure which makes getting started with a new Elixir application much easier. You don’t have to use it to create a new Elixir project but it is highly recommended and is used by most Elixir developers.

A simple "Hello, World!" application can be created with mix as follows:

$ mix new hello_world
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/hello_world.ex
* creating test
* creating test/test_helper.exs
* creating test/hello_world_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd hello_world
    mix test

Run "mix help" for more commands.

This command mix new hello_world created a new directory named hello_world and set up a basic Elixir application within it:

$ cd hello_world
$ tree
.
├── README.md
├── lib
│   └── hello_world.ex
├── mix.exs
└── test
    ├── hello_world_test.exs
    └── test_helper.exs

3 directories, 5 files

The lib/hello_world.ex file contains this code:

defmodule HelloWorld do
  @moduledoc """
  Documentation for `HelloWorld`.
  """

  @doc """
  Hello world.

  ## Examples

      iex> HelloWorld.hello()
      :world

  """
  def hello do
    :world
  end
end

It only contains a single function, hello/0, which returns the atom :world.

This structure serves as a starting point for your application. The complexity of the structure may grow as your application grows or uses more sophisticated frameworks such as Phoenix.

iex -S mix

To start an iex with the code of your current project, you can use iex -S mix.

$ iex -S mix
Compiling 1 file (.ex)
Generated hello_world app
Erlang/OTP 26 [...]

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

In this iex you now have access to all the functions defined in your project:

iex(1)> HelloWorld.hello()
:world
iex(2)>

Code Formatting with mix format

mix format is another powerful feature of mix that automatically formats your Elixir source code files according to a set of standard conventions. This helps keep your code clean and consistent, and can save you and your team a lot of time in code reviews.

You can run mix format in the root directory of your application:

$ mix format

This command will format all Elixir files in your project.

It’s a good habit to run mix format before committing any code to ensure that all code follows the same conventions.

Testing with mix test

Elixir promotes a test-driven development (TDD) approach, and mix makes it easy to create, manage, and run tests.

When you create a new project using mix, a test directory is created with a structure mirroring that of the lib directory. This is where all of your test files will reside.

Let’s create a simple Elixir module and corresponding test.

lib/hello_world.ex

defmodule HelloWorld do
  def greet(name) do
    "Hello, #{name}!"
  end
end

Now, let’s write a test for the greet function.

test/hello_world_test.exs

defmodule HelloWorldTest do
  use ExUnit.Case
  doctest HelloWorld

  test "greeting the world" do
    assert HelloWorld.greet("world") == "Hello, world!"
  end
end

The test macro defines a test, while assert checks that the actual result of the function matches the expected result.

You can now run the tests using mix test:

$ mix test
....

Finished in 0.05 seconds
1 test, 0 failures

Randomized with seed 12345

In this example, mix test ran 1 test, all of which passed.

Elixir also supports more complex testing scenarios, such as setup and teardown operations, test tagging, and asynchronous testing. As you write more complex Elixir programs, mix test will become an indispensable part of your development workflow.

Custom mix Tasks

mix allows you to define custom tasks, making it a powerful tool for automating common development tasks. These custom tasks are Elixir scripts that can be run from the command line. For example, we can define a "Hello, world!" task.

Create a new directory lib/mix/tasks and a new file within this directory named start.ex:

lib/mix/tasks/start.ex

defmodule Mix.Tasks.Start do
  use Mix.Task

  def run(_) do (1)
    IO.puts "Hello world!"
  end
end
1The run(_) function is the entry point for our task. It gets called when we run the task.

Now, running the command mix start will print "Hello, world!" to the terminal:

$ mix start
Compiling 1 file (.ex)
Generated hello_world app
Hello world!

The .ex file gets compiled and the start task gets run. The compile step is only done when needed. If we call mix start a second time, no compile is needed:

$ mix start
Hello world!

mix is a vast topic, and we’ve only scratched the surface. But this should give you a basic understanding of how mix can be utilized in an Elixir application.