Tuples

https://hexdocs.pm/elixir/Tuple.html

tuple_size

Lists

# Literal Form
[]
[1]
[1, 2, 3]
[head | tail] = [1, 2, 3]
head == 1
tail == [2, 3]

https://hexdocs.pm/elixir/List.html

Maps

Ce sont les dictionnaires d’Elixir.

# If the key is an atom:
%{atom_key: 1}
 
# If the key is a different type:
%{1 => :atom_value}
 
# You can even mix these if the atom form comes second:
%{"first_form" => :a, atom_form: :b}

Voir documentation https://hexdocs.pm/elixir/Map.html

Modules attributes

defmodule Example do
 
  # Defines the attribute as the value 1
  @constant_number 1
 
  def example_value() do
    # Returns the value 1
    @constant_number
  end
end

Cond

cond do
  x > 10 -> :this_might_be_the_way
  y < 7 -> :or_that_might_be_the_way
  true -> :this_is_the_default_way
end

Case

age = 15
 
case age do
  0 -> 'infant'
  age when age < 4 -> 'baby'
  age when age < 13 -> 'child'
  age when age < 18 -> 'teenager'
  _ -> 'adult'
end
 
# => 'teenager'

Default argument

def guess(number \\ 5)
def guess(number) when number != 5, do: false
def guess(number) when number == 5, do: true
 
guess()
# => true

Guards

https://hexdocs.pm/elixir/master/patterns-and-guards.html#guards

def empty?(list) when is_list(list) and length(list) == 0 do
  true
end

Multiple Clause Functions

# pattern matching the argument
def number(7) do
  "Awesome, that's my favorite"
end
 
# using a guard
def number(n) when is_integer(n) do
  "That's not my favorite"
end
 
def number(_n) do
  "That's not even a number!"
end

Pattern Matching

https://exercism.org/tracks/elixir/concepts/pattern-matching

{:ok, number, _} = {:ok, 5, [4.5, 6.3]}
number
# => 5 is bound to this variable
  • The pin operator ^ can be used to prevent rebounding a variable and instead pattern match against its existing value.
number = 10
{:ok, ^number, _} = {:ok, 5, [4.5, 6.3]}
# => ** (MatchError) no match of right hand side value: {:ok, 5, [4.5, 6.3]}
  • Pattern matches may also occur in a function clause head, so that only arguments that match the pattern will invoke the function.

  • Variables can be bound in a function clause pattern match.

Pipe Operator

The |> operator is called the pipe operator. It can be used to chain function calls together in such a way that the value returned by the previous function call is passed as the first argument to the next function call.

String.replace_suffix(String.upcase(String.duplicate("go ", 3)), " ", "!")
 
# versus
 
"go "
|> String.duplicate(3)
|> String.upcase()
|> String.replace_suffix(" ", "!")

Char list

'hello'
 
[65, 66, 67]
# => 'ABC'
 
[first_letter | _] = 'cat'
first_letter

Strings

Strings in Elixir are delimited by double quotes, and they are encoded in UTF-8:

"Hi!"

Strings can be concatenated using the <>/2 operator:

"Welcome to" <> " " <> "New York"
# => "Welcome to New York"

Strings in Elixir support interpolation using the #{} syntax:

"6 * 7 = #{6 * 7}"
# => "6 * 7 = 42"

Any Elixir expression is valid inside the interpolation. If a string is given, the string is interpolated as is. If any other value is given, Elixir will attempt to convert it to a string.

"Elixir can convert booleans to strings: #{true}"
# => "Elixir can convert booleans to strings: true"
 
"And #{["lists", ", ", "too!"]}"
# => "And lists, too!"

Recursion

Recursive functions are functions that call themselves.

A recursive function needs to have at least one base case and at least one recursive case.

A base case returns a value without calling the function again. A recursive case calls the function again, modifying the input so that it will at some point match the base case.

Very often, each case is written in its own function clause.

# base case
def count([]), do: 0
 
# recursive case
def count([_head | tail]), do: 1 + count(tail)

A recursive function can have many base cases and/or many recursive cases. For example the Fibonacci sequence is a recursive sequence with two base cases:

def fibonacci(0), do: 0
def fibonacci(1), do: 1
def fibonacci(n), do: fibonacci(n - 1) + fibonacci(n - 2)

Counting the number of occurrences of some given value x in a list has two recursive cases:

def count_occurrences([], _x), do: 0
def count_occurrences([x | tail], x), do: 1 + count_occurrences(tail, x)
def count_occurrences([_ | tail], x), do: count_occurrences(tail, x)

Loops through recursion

Due to immutability, loops in Elixir are written differently from imperative languages. For example, loops commonly look like:

for(i = 0; i < array.size; i++) {
  # do something with array[i]
}

In a functional language, mutating i (by calling i++) is not possible. Thus, loops have to be implemented with recursion.

The equivalent of a for loop in Elixir would look like this:

def loop([]), do: nil
 
def loop([head | tail]) do
  do_something(head)
  loop(tail)
end

In practice, iterating over lists and other enumerable data structures is most often done using the Enum module. Under the hood, functions from the Enum module are implemented using recursion.

@spec types

https://hexdocs.pm/elixir/typespecs.html

Ces deux façon d’écrire sont équivalentes les @spec :

@spec format_address(address :: address()) :: String.t()  
@spec format_address(address) :: String.t
def format_address(%{street: street, postal_code: postal_code, city: city}) do  
 format_address({street, postal_code, city})  
end  
  
def format_address({street, postal_code, city}) do  
 """  
  #{String.upcase(street)}  
 #{String.upcase(postal_code)} #{String.upcase(city)}  
 """  
end