Table of Contents
- Basic Types
- Operators
- Pattern Matching
- case, cond and if
- Binaries
- Keyword Lists and Maps
- Modules and functions
- Recursion
- Enumerables and Streams
- Processes
- IO and the file system
- alias, require, and import
- Module Attributes
- Structs
- Protocols
- Sigils
- try, catch, and rescue
- Optional Syntax
iex> 1 # integer
iex> 0x1F # integer
iex> 1.0 # float
iex> true # boolean
iex> :atom # atom / symbol
iex> "elixir" # string
iex> [1, 2, 3] # list
iex> {1, 2, 3} # tuple
Docs
iex> h trunc/1
Anonymous functions
iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
Linked lists:
iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
When Elixir sees a list of printable ASCII numbers, Elixir will print that as a charlist:
iex> [104, 101, 108, 108, 111]
'hello'
iex> i 'hello'
Raw representation
[104, 101, 108, 108, 111]
Tuples:
iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2
iex> put_elem(tuple, 2, :e)
{:a, :b, :e, :
iex> elem(tuple, 1)
"hello"
Arithmetic:
+
-
*
/
List manipulation:
++
--
String concatenation:
<>
Boolean:
or
and
not
iex> 1 and true
** (BadBooleanError) expected a boolean on left-side of "and", got: 1
||
&&
!
iex> 1 || true
1
iex> !nil
true
Comparison:
==
!=
===
<=
- `>=
<
>
Comparing two different data types:
iex> 1 < :atom
true
Sorting order, lower to higher:
number, atom, reference, function, port, pid, tuple, map, list, bitstring
Match operator
iex> x = 1
1
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1
Pattern Matching
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
Errors:
iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}
iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value: [:hello, "world", 42]
Lists:
iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
Head/tail
iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]
Use the pin operator ^ when you want to pattern match against a variable’s existing value rather than rebinding the variable.
iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
Pattern matching w/ pin operator:
iex> x = 1
1
iex> [^x, 2, 3] = [1, 2, 3]
[1, 2, 3]
iex> {y, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}
Underscore:
iex> [head | _] = [1, 2, 3]
[1, 2, 3]
iex> head
1
case
iex> case {1, 2, 3} do
...> {4, 5, 6} ->
...> "This clause won't match"
...> {1, x, 3} ->
...> "This clause will match and bind x to 2 in this clause"
...> _ ->
...> "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"
cond
iex> cond do
...> 2 + 2 == 5 ->
...> "This will not be true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> 1 + 1 == 2 ->
...> "But this will"
...> end
"But this will"
if / unless
iex> if true do
...> "This works!"
...> end
"This works!"
iex> unless true do
...> "This will never be seen"
...> end
nil
Variable scoping:
iex> x = 1
1
iex> if true do
...> x = x + 1
...> end
2
iex> x
1
iex> x = if true do
...> x + 1
...> else
...> x
...> end
iex > x
2
iex> string = "hello"
"hello"
iex> is_binary(string)
true
You can use a ? in front of a character literal to reveal its code point:
iex> ?a
97
iex> string = "héllo"
"héllo"
iex> String.length(string)
5
iex> byte_size(string)
6
iex > String.length("👩🚒")
1
iex> String.codepoints("👩🚒")
["👩", "", "🚒"]
iex> String.graphemes("👩🚒")
["👩🚒"]
To see the exact bytes that a string would be stored in a file, concatenate the null byte <<0>> to it:
iex> "hełło" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>
iex> IO.inspect("hełło", binaries: :as_binaries)
<<104, 101, 197, 130, 197, 130, 111>>
A bitstring is a fundamental data type in Elixir, denoted with the <<>> syntax. A bitstring is a contiguous sequence of bits in memory.
iex> <<42>> == <<42::8>>
true
iex> <<3::4>>
<<3::size(4)>>
By default, 8 bits (i.e. 1 byte) is used to store each number in a bitstring, but you can manually specify the number of bits via a ::n modifier to denote the size in n bits:
iex> <<42>> == <<42::8>>
true
iex> <<3::4>>
<<3::size(4)>>
For example, the decimal number 3 when represented with 4 bits in base 2 would be 0011, which is equivalent to the values 0, 0, 1, 1, each stored using 1 bit:
iex> <<0::1, 0::1, 1::1, 1::1>> == <<3::4>>
true
257 in base 2 would be represented as 100000001, but since we have reserved only 8 bits for its representation (by default), the left-most bit is ignored and the value becomes truncated to 00000001, or simply 1 in decimal.
iex> <<1>> == <<257>>
true
iex> is_binary(<<3::4>>)
false
iex> is_bitstring(<<0, 255, 42>>)
true
iex> is_binary(<<0, 255, 42>>)
true
iex> is_binary(<<42::16>>)
true
Pattern matching:
iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x::binary>> = <<0, 1, 2, 3>> # match binary of unknown size w/ `binary` modifier
<<0, 1, 2, 3>>
binary-size modifier:
iex> <<head::binary-size(2), rest::binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> head
<<0, 1>>
iex> rest
<<2, 3>>
A string is a UTF-8 encoded binary, where the code point for each character is encoded using 1 to 4 bytes.
iex> is_binary("hello")
true
<> is actually a binary concatenation operator:
iex> "a" <> "ha"
"aha"
iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>
Given that strings are binaries, we can also pattern match on strings:
iex> <<head, rest::binary>> = "banana"
"banana"
iex> head == ?b
true
iex> rest
"anana"
A charlist is a list of integers where all the integers are valid code points. In practice, you will not come across them often, only in specific scenarios such as interfacing with older Erlang libraries that do not accept binaries as arguments.
iex> 'hello'
'hello'
iex> [?h, ?e, ?l, ?l, ?o]
'hello'
if you are storing a list of integers that happen to range between 0 and 127, by default IEx will interpret this as a charlist and it will display the corresponding ASCII characters.
iex> heartbeats_per_minute = [99, 97, 116]
'cat'
String (binary) concatenation uses the <> operator but charlists, being lists, use the list concatenation operator ++:
iex> 'this ' <> 'fails'
** (ArgumentError) expected binary argument in <> operator but got: 'this '
(elixir) lib/kernel.ex:1821: Kernel.wrap_concatenation/3
(elixir) lib/kernel.ex:1808: Kernel.extract_concatenations/2
(elixir) expanding macro: Kernel.<>/2
iex:1: (file)
iex> 'this ' ++ 'works'
'this works'
iex> "he" ++ "llo"
** (ArgumentError) argument error
:erlang.++("he", "llo")
iex> "he" <> "llo"
"hello"
Keyword lists are a data-structure used to pass options to functions.
- Keys must be atoms.
- Keys are ordered, as specified by the developer.
- Keys can be given more than once.
iex> String.split("1 2 3", " ")
["1", "", "2", "", "3"]
iex> String.split("1 2 3", " ", [trim: true])
["1", "2", "3"]
# when a keyword list is the last argument of a function, we can skip the brackets and write:
iex> String.split("1 2 3", " ", trim: true)
["1", "2", "3"]
Keyword lists are 2-item tuples where the first element (the key) is an atom and the second element can be any value. Both representations are the same:
iex> [{:trim, true}] == [trim: true]
true
We can use all operations available to lists:
iex> list ++ [c: 3]
[a: 1, b: 2, c: 3]
iex> list[:a]
1
iex> list[:b]
2
Values added to the front are the ones fetched:
iex> new_list = [a: 0] ++ list
[a: 0, a: 1, b: 2]
iex> new_list[:a]
0
Whenever you need a key-value store, maps are the “go to” data structure in Elixir.
- Maps allow any value as a key.
- Maps’ keys do not follow any ordering.
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil
Pattern matching:
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> a
1
Variables can be used when accessing, matching and adding map keys:
iex> n = 1
1
iex> map = %{n => :one}
%{1 => :one}
iex> map[n]
:one
Map module:
iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.put(%{:a => 1, 2 => :b}, :c, 3)
%{2 => :b, :a => 1, :c => 3}
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
Update syntax:
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{map | 2 => "two"}
%{2 => "two", :a => 1}
When all the keys in a map are atoms, you can use the keyword syntax for convenience:
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
Syntax for accessing atom keys:
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map.a
1
iex> map.c
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
do blocks are nothing more than a syntax convenience on top of keywords.
iex> if true do
...> "This will be seen"
...> else
...> "This won't"
...> end
"This will be seen"
We can rewrite the above to:
iex> if true, do: "This will be seen", else: "This won't"
"This will be seen"
iex> users = [
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
iex> users[:john].age
27
Update value w/ put_in:
iex> users = put_in users[:john].age, 31
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
The update_in/2 macro is similar but allows us to pass a function that controls how the value changes. For example, let’s remove “Clojure” from Mary’s list of languages:
iex> users = update_in users[:mary].languages, fn languages -> List.delete(languages, "Clojure") end
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}
]
Elixir projects are usually organized into three directories:
- _build - contains compilation artifacts
- lib - contains Elixir code (usually .ex files)
- test - contains tests (usually .exs files)
iex> defmodule Math do
...> def sum(a, b) do
...> a + b
...> end
...> end
iex> Math.sum(1, 2)
3
Compile using elixirc
to generate a BEAM file containing bytecode;
$ elixirc math.ex # will output Elixir.Math.beam
If we start iex again, our module definition will be available (iex is started in the same directory):
iex> Math.sum(1, 2)
3
.ex
files are meant to be compiled while .exs
files are used for scripting.
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts Math.sum(1, 2)
And execute it as:
$ elixir math.exs
defmodule Math do
def sum(a, b) do
do_sum(a, b)
end
defp do_sum(a, b) do
a + b
end
end
IO.puts Math.sum(1, 2) #=> 3
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)
Function declarations also support guards and multiple clauses. If a function has several clauses, Elixir will try each clause until it finds one that matches.
defmodule Math do
def zero?(0) do
true
end
def zero?(x) when is_integer(x) do
false
end
end
IO.puts Math.zero?(0) #=> true
IO.puts Math.zero?(1) #=> false
IO.puts Math.zero?([1, 2, 3]) #=> ** (FunctionClauseError)
IO.puts Math.zero?(0.0) #=> ** (FunctionClauseError)
We can edit it to look like this and it will provide the same behaviour:
defmodule Math do
def zero?(0), do: true
def zero?(x) when is_integer(x), do: false
end
iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function(fun)
true
iex> fun.(0)
true
You can also capture operators:
iex> add = &+/2
&:erlang.+/2
iex> add.(1, 2)
3
The capture syntax can also be used as a shortcut for creating functions:
iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2
iex> fun2 = &"Good #{&1}"
#Function<6.127694169/1 in :erl_eval.expr/5>
iex> fun2.("morning")
"Good morning"
The &1 represents the first argument passed into the function. &(&1 + 1) above is exactly the same as fn x -> x + 1 end
defmodule Concat do
def join(a, b, sep \\ " ") do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
If a function with default values has multiple clauses, it is required to create a function head (a function definition without a body) for declaring defaults:
defmodule Concat do
# A function head declaring defaults
def join(a, b \\ nil, sep \\ " ")
def join(a, b, _sep) when is_nil(b) do
a
end
def join(a, b, sep) do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello") #=> Hello
Example:
defmodule Recursion do
def print_multiple_times(msg, n) when n > 0 do
IO.puts(msg)
print_multiple_times(msg, n - 1)
end
def print_multiple_times(_msg, 0) do
:ok
end
end
Recursion.print_multiple_times("Hello!", 3)
# Hello!
# Hello!
# Hello!
:ok
Reduce and map:
defmodule Math do
def sum_list([head | tail], accumulator) do
sum_list(tail, head + accumulator)
end
def sum_list([], accumulator) do
accumulator
end
end
IO.puts Math.sum_list([1, 2, 3], 0) #=> 6
Double all of the values in a list:
defmodule Math do
def double_each([head | tail]) do
[head * 2 | double_each(tail)]
end
def double_each([]) do
[]
end
end
The examples above could be written as:
iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end)
6
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
Or, using the capture syntax:
iex> Enum.reduce([1, 2, 3], 0, &+/2)
6
iex> Enum.map([1, 2, 3], &(&1 * 2))
[2, 4, 6]
All the functions in the Enum module are eager. Many functions expect an enumerable and return a list back:
iex> odd? = &(rem(&1, 2) != 0)
#Function<6.80484245/1 in :erl_eval.expr/5>
iex> Enum.filter(1..3, odd?)
[1, 3]
Pipe operator
iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum()
7500000000
Equivalent of
iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000
As an alternative to Enum, Elixir provides the Stream module which supports lazy operations:
iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
7500000000
Instead of generating intermediate lists, streams build a series of computations that are invoked only when we pass the underlying stream to the Enum module. Streams are useful when working with large, possibly infinite, collections.
Stream.cycle/1 can be used to create a stream that cycles a given enumerable infinitely.
iex> stream = Stream.cycle([1, 2, 3])
#Function<15.16982430/2 in Stream.unfold/2>
iex> Enum.take(stream, 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
Stream.unfold/2
can be used to generate values from a given initial value:
iex> stream = Stream.unfold("hełło", &String.next_codepoint/1)
#Function<39.75994740/2 in Stream.unfold/2>
iex> Enum.take(stream, 3)
["h", "e", "ł"]
Fetch the first 10 lines of the file you have selected;
iex> stream = File.stream!("path/to/file")
%File.Stream{
line_or_bytes: :line,
modes: [:raw, :read_ahead, :binary],
path: "path/to/file",
raw: true
}
iex> Enum.take(stream, 10)
sAll code runs inside processes. Processes are isolated from each other, run concurrent to one another and communicate via message passing. Processes are not only the basis for concurrency in Elixir, but they also provide the means for building distributed and fault-tolerant programs.
Elixir’s processes should not be confused with operating system processes. Processes in Elixir are extremely lightweight in terms of memory and CPU (even compared to threads as used in many other programming languages). Because of this, it is not uncommon to have tens or even hundreds of thousands of processes running simultaneously.
spawn/1 takes a function which it will execute in another process.
iex> pid = spawn(fn -> 1 + 2 end)
#PID<0.43.0>
iex> Process.alive?(pid)
false
Retrieve the PID of the current process by calling self/0:
iex> self()
#PID<0.41.0>
iex> Process.alive?(self())
true
iex> send(self(), {:hello, "world"})
{:hello, "world"}
iex> receive do
...> {:hello, msg} -> msg
...> {:world, _msg} -> "won't match"
...> end
"world"
Timeout:
iex> receive do
...> {:hello, msg} -> msg
...> after
...> 1_000 -> "nothing after 1s"
...> end
"nothing after 1s"
If we want the failure in one process to propagate to another one, we should link them. This can be done with spawn_link/1:
iex> self()
#PID<0.41.0>
iex> spawn_link(fn -> raise "oops" end)
** (EXIT from #PID<0.41.0>) evaluator process exited with reason: an exception was raised:
** (RuntimeError) oops
(stdlib) erl_eval.erl:668: :erl_eval.do_apply/6
[error] Process #PID<0.289.0> raised an exception
** (RuntimeError) oops
(stdlib) erl_eval.erl:668: :erl_eval.do_apply/6
Tasks build on top of the spawn functions to provide better error reports and introspection:
iex> Task.start(fn -> raise "oops" end)
{:ok, #PID<0.55.0>}
15:22:33.046 [error] Task #PID<0.55.0> started from #PID<0.53.0> terminating
** (RuntimeError) oops
(stdlib) erl_eval.erl:668: :erl_eval.do_apply/6
(elixir) lib/task/supervised.ex:85: Task.Supervised.do_apply/2
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Function: #Function<20.99386804/0 in :erl_eval.expr/5>
Args: []
We can write processes that loop infinitely, maintain state, and send and receive messages. As an example, let’s write a module that starts new processes that work as a key-value store:
# kv.exs
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
end
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
iex> {:ok, pid} = KV.start_link()
{:ok, #PID<0.62.0>}
iex> send(pid, {:get, :hello, self()})
{:get, :hello, #PID<0.41.0>}
iex> flush()
nil
:ok
iex> send(pid, {:put, :hello, :world})
{:put, :hello, :world}
iex> send(pid, {:get, :hello, self()})
{:get, :hello, #PID<0.41.0>}
iex> flush()
:world
:ok
Register processes with name:
iex> Process.register(pid, :kv)
true
iex> send(:kv, {:get, :hello, self()})
{:get, :hello, #PID<0.41.0>}
iex> flush()
:world
:ok
Maintaining state using Agents:
iex> {:ok, pid} = Agent.start_link(fn -> %{} end)
{:ok, #PID<0.72.0>}
iex> Agent.update(pid, fn map -> Map.put(map, :hello, :world) end)
:ok
iex> Agent.get(pid, fn map -> Map.get(map, :hello) end)
:world
iex> IO.puts("hello world")
hello world
:ok
iex> IO.gets("yes or no? ")
yes or no? yes
"yes\n"
iex> iex> IO.puts(:stderr, "hello world")
hello world
:ok
By default, files are opened in binary mode:
iex> {:ok, file} = File.open("path/to/file/hello", [:write])
{:ok, #PID<0.47.0>}
iex> IO.binwrite(file, "world")
:ok
iex> File.close(file)
:ok
iex> File.read("path/to/file/hello")
{:ok, "world"}
Some other functions under File
:
- rm
- mkdir
- mkdir_p
- cp
- cp_r
- rm_rf
Handling errors:
iex> File.read!("path/to/file/hello")
"world"
iex> File.read("path/to/file/unknown")
{:error, :enoent}
iex> File.read!("path/to/file/unknown")
** (File.Error) could not read file "path/to/file/unknown": no such file or directory
iex> Path.join("foo", "bar")
"foo/bar"
iex> Path.expand("~/hello")
"/Users/jose/hello"
iex> {:ok, file} = File.open("hello", [:write])
{:ok, #PID<0.47.0>}
Given a file is a process, when you write to a file that has been closed, you are actually sending a message to a process which has been terminated:
iex> File.close(file)
:ok
iex> IO.write(file, "is anybody out there")
{:error, :terminated}
Most of the IO functions in Elixir also accept either “iodata” or “chardata”.
Copying can be quite expensive for large strings. Elixir allow you to pass list of strings:
name = "Mary"
IO.puts(["Hello ", name, "!"])
Imagine you have a list of values, such as ["apple", "banana", "lemon"] that you want to write to disk separated by commas. How can you achieve this?
One option is to use Enum.join/2 and convert the values to a string:
iex> Enum.join(["apple", "banana", "lemon"], ",")
"apple,banana,lemon"
However, we can pass a list of strings to the IO/File functions. So instead we can do:
iex> Enum.intersperse(["apple", "banana", "lemon"], ",")
["apple", ",", "banana", ",", "lemon"]
“iodata” and “chardata” do not only contain strings, but they may contain arbitrary nested lists of strings too:
iex> IO.puts(["apple", [",", "banana", [",", "lemon"]]])
apple,banana,lemon
:ok
Print comma separated list of values by using ?, as separator, which is the integer representing a comma (44):
iex(19)> IO.puts(["apple", ?,, "banana", ?,, "lemon"])
apple,banana,lemon
:ok
- For iodata, the integers represent bytes.
- For chardata, the integers represent Unicode codepoints.
- For ASCII characters, the byte representation is the same as the codepoint representation, so it fits both classifications.
The default IO device works with chardata, which means we can do:
iex> IO.puts([?O, ?l, ?á, ?\s, "Mary", ?!])
Olá Mary!
:ok
charlist construct:
iex> ~c"hello"
~c"hello"
summary:
- iodata and chardata are lists of binaries and integers. Those binaries and integers can be arbitrarily nested inside lists. Their goal is to give flexibility and performance when working with IO devices and files
- the choice between iodata and chardata depends on the encoding of the IO device. If the file is opened without encoding, the file expects iodata, and the functions in the IO module starting with bin* must be used. The default IO device (:stdio) and files opened with :utf8 encoding work expect chardata and work with the remaining functions in the IO module
- charlists are a special case of chardata, where it exclusively uses a list of integers Unicode codepoints. They can be created with the ~c sigil. Lists of integers are automatically printed using the ~c sigil if all integers in a list represent printable ASCII codepoints.
alias, require, import
# Alias the module so it can be called as Bar instead of Foo.Bar
alias Foo.Bar, as: Bar
# Require the module in order to use its macros
require Foo
# Import functions from Foo so they can be called without the `Foo.` prefix
import Foo
# Invokes the custom code defined in Foo as an extension point
use Foo
alias Math.List
Is the same as:
alias Math.List, as: List
Public functions in modules are globally available, but in order to use macros, you need to opt-in by requiring the module they are defined in.
iex> Integer.is_odd(3)
** (UndefinedFunctionError) function Integer.is_odd/1 is undefined or private. However, there is a macro with the same name and arity. Be sure to require Integer if you intend to invoke this macro
(elixir) Integer.is_odd(3)
iex> require Integer
Integer
iex> Integer.is_odd(3)
true
To use the duplicate/2 function from the List module several times, we can import it:
iex> import List, only: [duplicate: 2]
List
iex> duplicate(:ok, 3)
[:ok, :ok, :ok]
We can import specific macros or functions inside function definitions:
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
Note that imports are generally discouraged in the language. When working on your own code, prefer alias to import.
The use macro is frequently used as an extension point. This means that, when you use a module FooBar, you allow that module to inject any code in the current module, such as importing itself or other modules, defining new functions, setting a module state, etc.
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
Behind the scenes, use requires the given module and then calls the using/1 callback on it allowing the module to inject some code into the current context.
An alias in Elixir is a capitalized identifier (like String, Keyword, etc) which is converted to an atom during compilation.
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String" == String
true
iex> List.flatten([1, [2], 3])
[1, 2, 3]
iex> :"Elixir.List".flatten([1, [2], 3])
[1, 2, 3]
That’s the mechanism we use to call Erlang modules:
iex> :lists.flatten([1, [2], 3])
[1, 2, 3]
defmodule Foo do
defmodule Bar do
end
end
The example above will define two modules: Foo and Foo.Bar. The second can be accessed as Bar inside Foo as long as they are in the same lexical scope.
The above could also be written as:
defmodule Foo.Bar do
end
defmodule Foo do
alias Foo.Bar
# Can still access it as `Bar`
end
Alias multiple modules at once:
alias MyApp.{Foo, Bar, Baz}
Module attributes in Elixir serve three purposes:
- They serve to annotate the module, often with information to be used by the user or the VM.
- They work as constants.
- They work as a temporary module storage to be used during compilation.
@moduledoc
- provides documentation for the current module.@doc
- provides documentation for the function or macro that follows the attribute.@spec
- provides a typespec for the function that follows the attribute.@behaviour
- (notice the British spelling) used for specifying an OTP or user-defined behaviour.
defmodule MyServer do
@moduledoc "My server code."
end
defmodule Math do
@moduledoc """
Provides math-related functions.
## Examples
iex> Math.sum(1, 2)
3
"""
@doc """
Calculates the sum of two numbers.
"""
def sum(a, b), do: a + b
end
defmodule MyServer do
@initial_state %{host: "127.0.0.1", port: 3456}
IO.inspect @initial_state
end
Functions can be called:
defmodule MyApp.Status do
@service URI.parse("https://example.com")
def status(email) do
SomeHttpClient.get(@service)
end
end
The function above will be called at compilation time and its return value, not the function call itself, is what will be substituted in for the attribute. So the above will effectively compile to this:
defmodule MyApp.Status do
def status(email) do
SomeHttpClient.get(%URI{
authority: "example.com",
host: "example.com",
port: 443,
scheme: "https"
})
end
end
Every time an attribute is read inside a function, Elixir takes a snapshot of its current value. Therefore if you read the same attribute multiple times inside multiple functions, you may end-up making multiple copies of it.
Instead of this:
def some_function, do: do_something_with(@example)
def another_function, do: do_something_else_with(@example)
Prefer this:
def some_function, do: do_something_with(example())
def another_function, do: do_something_else_with(example())
defp example, do: @example
Normally, repeating a module attribute will cause its value to be reassigned, but there are circumstances where you may want to configure the module attribute so that its values are accumulated:
defmodule Foo do
Module.register_attribute __MODULE__, :param, accumulate: true
@param :foo
@param :bar
# here @param == [:bar, :foo]
end
defmodule MyTest do
use ExUnit.Case, async: true
@tag :external
@tag os: :unix
test "contacts external service" do
# ...
end
end
Structs are extensions built on top of maps that provide compile-time checks and default values.
To define a struct, the defstruct construct is used:
iex> defmodule User do
...> defstruct name: "John", age: 27
...> end
iex> %User{}
%User{age: 27, name: "John"}
iex> %User{name: "Jane"}
%User{age: 27, name: "Jane"}
iex> %User{oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "John"}
iex> john = %User{}
iex> john.name
"John"
iex> jane = %{john | name: "Jane"}
%User{age: 27, name: "Jane"}
Pattern matching
iex> %User{name: name} = john
%User{age: 27, name: "John"}
iex> name
"John"
Underneath they're bare maps
iex> is_map(john)
true
iex> john.__struct__
User
iex> jane = Map.put(%User{}, :name, "Jane")
%User{age: 27, name: "Jane"}
iex> Map.merge(jane, %User{name: "John"})
%User{age: 27, name: "John"}
iex> Map.keys(jane)
[:__struct__, :age, :name]
If you don’t specify a default key value when defining a struct, nil will be assumed:
iex> defmodule User do
...> defstruct [:email, name: "John", age: 27]
...> end
iex> %User{}
%User{age: 27, email: nil, name: "John"}
Enforce certain keys to be specified:
iex> defmodule Car do
...> @enforce_keys [:make]
...> defstruct [:model, :make]
...> end
iex> %Car{}
** (ArgumentError) the following keys must also be given when building struct Car: [:make]
expanding struct: Car.__struct__/1
Consider a simple utility module that would tell us the type of input variable:
defmodule Utility do
def type(value) when is_binary(value), do: "string"
def type(value) when is_integer(value), do: "integer"
# ... other implementations ...
end
If the use of this module were confined to your own project, you would be able to keep defining new type/1 functions for each new data type. However, this code could be problematic if it was shared as a dependency by multiple apps because there would be no easy way to extend its functionality.
This is where protocols can help us: protocols allow us to extend the original behavior for as many data types as we need. That’s because dispatching on a protocol is available to any data type that has implemented the protocol and a protocol can be implemented by anyone, at any time.
defprotocol Utility do
@spec type(t) :: String.t()
def type(value)
end
defimpl Utility, for: BitString do
def type(_value), do: "string"
end
defimpl Utility, for: Integer do
def type(_value), do: "integer"
end
iex> Utility.type("foo")
"string"
iex> Utility.type(123)
"integer"
We have two idioms for checking how many items there are in a data structure: length and size.
- length means the information must be computed. For example, length(list) needs to traverse the whole list to calculate its length.
- tuple_size(tuple) and byte_size(binary) do not depend on the tuple and binary size as the size information is pre-computed in the data structure.
We could implement a generic Size protocol that all data structures for which size is pre-computed would implement:
defprotocol Size do
@doc "Calculates the size (and not the length!) of a data structure"
def size(data)
end
defimpl Size, for: BitString do
def size(string), do: byte_size(string)
end
defimpl Size, for: Map do
def size(map), do: map_size(map)
end
defimpl Size, for: Tuple do
def size(tuple), do: tuple_size(tuple)
end
Usage:
iex> Size.size("foo")
3
iex> Size.size({:ok, "hello"})
2
iex> Size.size(%{label: "some label"})
1
Passing a data type that doesn’t implement the protocol raises an error:
iex> Size.size([1, 2, 3])
** (Protocol.UndefinedError) protocol Size not implemented for [1, 2, 3]
It’s possible to implement protocols for all Elixir data types:
- Atom
- BitString
- Float
- Function
- Integer
- List
- Map
- PID
- Port
- Reference
- Tuple
If desired, you could come up with your own semantics for the size of your struct.
defmodule User do
defstruct [:name, :age]
end
defimpl Size, for: User do
def size(_user), do: 2
end
Elixir allows us to derive a protocol implementation based on the Any implementation. Let’s first implement Any as follows:
defimpl Size, for: Any do
def size(_), do: 0
end
However, should we be fine with the implementation for Any, in order to use such implementation we would need to tell our struct to explicitly derive the Size protocol:
defmodule OtherUser do
@derive [Size]
defstruct [:name, :age]
end
iex> to_string :hello
"hello"
Notice that string interpolation in Elixir calls the to_string function:
iex> "age: #{25}"
"age: 25"
The snippet above only works because numbers implement the String.Chars protocol. Passing a tuple, for example, will lead to an error:
iex> tuple = {1, 2, 3}
{1, 2, 3}
iex> "tuple: #{tuple}"
** (Protocol.UndefinedError) protocol String.Chars not implemented for {1, 2, 3}
When there is a need to “print” a more complex data structure, one can use the inspect function, based on the Inspect protocol:
iex> "tuple: #{inspect tuple}"
"tuple: {1, 2, 3}"
The Inspect protocol is the protocol used to transform any data structure into a readable textual representation.
iex> {1, 2, 3}
{1, 2, 3}
iex> %User{}
%User{name: "john", age: 27}
iex> inspect &(&1+2)
"#Function<6.71889879/1 in :erl_eval.expr/5>"
A comprehension is made of three parts: generators, filters, and collectables.
iex> for n <- 1..4, do: n * n
[1, 4, 9, 16]
pattern matching
iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]
filtering:
iex> for n <- 0..5, rem(n, 3) == 0, do: n * n
[0, 9]
Bitstring generators
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
Transforming values in a map:
iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
%{"a" => 1, "b" => 4}
Regular expressions:
# A regular expression that matches strings which contain "foo" or "bar":
iex> regex = ~r/foo|bar/
~r/foo|bar/
iex> "foo" =~ regex
true
iex> "bat" =~ regex
false
Strings:
iex> ~s(this is a string with "double" quotes, not 'single' ones)
"this is a string with \"double\" quotes, not 'single' ones"
Char lists:
iex> ~c(this is a char list containing 'single quotes')
'this is a char list containing \'single quotes\''
Word lists:
iex> ~w(foo bar bat)
["foo", "bar", "bat"]
iex> ~w(foo bar bat)a
[:foo, :bar, :bat]
Interpolation and escaping:
iex> ~s(String with escape codes \x26 #{"inter" <> "polation"})
"String with escape codes & interpolation"
iex> ~S(String without escape codes \x26 without #{interpolation})
"String without escape codes \\x26 without \#{interpolation}"
The following escape codes can be used in strings and char lists:
- \ – single backslash
- \a – bell/alert
- \b – backspace
- \d - delete
- \e - escape
- \f - form feed
- \n – newline
- \r – carriage return
- \s – space
- \t – tab
- \v – vertical tab
- \0 - null byte
- \xDD - represents a single byte in hexadecimal (such as \x13)
- \uDDDD and \u{D...} - represents a Unicode codepoint in hexadecimal (such as \u{1F600})
%Date{}
iex> d = ~D[2019-10-31]
~D[2019-10-31]
iex> d.day
31
%Time{}
iex> t = ~T[23:00:07.0]
~T[23:00:07.0]
iex> t.second
7
%NaiveDateTime{}
iex> ndt = ~N[2019-10-31 23:00:07]
~N[2019-10-31 23:00:07]
%DateTime{}
iex> dt = ~U[2019-10-31 19:59:03Z]
~U[2019-10-31 19:59:03Z]
iex> %DateTime{minute: minute, time_zone: time_zone} = dt
~U[2019-10-31 19:59:03Z]
iex> minute
59
iex> time_zone
"Etc/UTC"
iex> sigil_r(<<"foo">>, 'i')
~r"foo"i
iex> defmodule MySigils do
...> def sigil_i(string, []), do: String.to_integer(string)
...> def sigil_i(string, [?n]), do: -String.to_integer(string)
...> end
iex> import MySigils
iex> ~i(13)
13
iex> ~i(42)n
-42
iex> raise "oops"
** (RuntimeError) oops
iex> raise ArgumentError, message: "invalid argument foo"
** (ArgumentError) invalid argument foo
Custom errors
iex> defmodule MyError do
iex> defexception message: "default message"
iex> end
iex> raise MyError
** (MyError) default message
iex> raise MyError, message: "custom message"
** (MyError) custom message
iex> try do
...> raise "oops"
...> rescue
...> e in RuntimeError -> e
...> end
%RuntimeError{message: "oops"}
Elixir developers rarely use the try/rescue construct. You can use pattern matching using the case construct:
iex> case File.read("hello") do
...> {:ok, body} -> IO.puts("Success: #{body}")
...> {:error, reason} -> IO.puts("Error: #{reason}")
...> end
The convention is to;
- create a function (foo) which returns {:ok, result} or {:error, reason} tuples
- and another function (foo!, same name but with a trailing !) that takes the same arguments as foo but which raises an exception if there’s an error.
One saying that is common in the Erlang community, as well as Elixir’s, is “fail fast” / “let it crash”. The idea behind let it crash is that, in case something unexpected happens, it is best to let the exception happen, without rescuing it.
try do
... some code ...
rescue
e ->
Logger.error(Exception.format(:error, e, __STACKTRACE__))
reraise e, __STACKTRACE__
end
iex> try do
...> Enum.each(-50..50, fn x ->
...> if rem(x, 13) == 0, do: throw(x)
...> end)
...> "Got nothing"
...> catch
...> x -> "Got #{x}"
...> end
"Got -39"
When a process dies of “natural causes” (e.g., unhandled exceptions), it sends an exit signal.
iex> spawn_link(fn -> exit(1) end)
** (EXIT from #PID<0.56.0>) evaluator process exited with reason: 1
iex> try do
...> exit("I am exiting")
...> catch
...> :exit, _ -> "not really"
...> end
"not really"
The try/after construct allows you to clean up after some action that could potentially raise an error. For example, we can open a file and use an after clause to close it–even if something goes wrong:
iex> {:ok, file} = File.open("sample", [:utf8, :write])
iex> try do
...> IO.write(file, "olá")
...> raise "oops, something went wrong"
...> after
...> File.close(file)
...> end
** (RuntimeError) oops, something went wrong
Elixir allows you to omit the try line:
iex> defmodule RunAfter do
...> def without_even_trying do
...> raise "oops"
...> after
...> IO.puts "cleaning up!"
...> end
...> end
iex> RunAfter.without_even_trying
cleaning up!
** (RuntimeError) oops
iex> x = 2
2
iex> try do
...> 1 / x
...> rescue
...> ArithmeticError ->
...> :infinity
...> else
...> y when y < 1 and y > -1 ->
...> :small
...> _ ->
...> :large
...> end
:small
Elixir syntax allows developers to omit delimiters in a few occasions to make code more readable. For example, we learned that parentheses are optional:
iex> length([1, 2, 3]) == length [1, 2, 3]
true
and that do-end blocks are equivalent to keyword lists:
# do-end blocks
iex> if true do
...> :this
...> else
...> :that
...> end
:this
# keyword lists
iex> if true, do: :this, else: :that
:this
Example:
if variable? do
Call.this()
else
Call.that()
end
- do-end blocks are equivalent to keywords:
if variable?, do: Call.this(), else: Call.that()
- Keyword lists as last argument do not require square brackets, but let’s add them:
if variable?, [do: Call.this(), else: Call.that()]
- Keyword lists are the same as lists of two-element tuples:
if variable?, [{:do, Call.this()}, {:else, Call.that()}]
Finally, parentheses are optional, but let’s add them:
if(variable?, [{:do, Call.this()}, {:else, Call.that()}])
Another example:
defmodule Math do
def add(a, b) do
a + b
end
end
defmodule(Math, [
{:do, def(add(a, b), [{:do, a + b}])}
])