Introduction to Elixir

July 12, 2018

I’m currently learning Elixir and this post represents some of my learnings summarized from the Elixir guide.

Getting Started

Basic Types

  • An atom is a constant whose name is its own value.
  • The booleans true and false are, in fact, atoms.
  • Anonymous functions can be created inline and are delimited by the keywords fn and end.
iex> add = fn a, b -> a + b end
iex> add.(1, 2)
  • Functions are “first class citizens” in Elixir meaning they can be passed as arguments to other functions in the same way as integers and strings.
  • Note that a dot (.) between the variable and parentheses is required to invoke an anonymous function. The dot ensures there is no ambiguity between calling an anonymous function named add and a named function add/2.
  • Elixir data structures are immutable. One advantage of immutability is that it leads to clearer code. You can freely pass the data around with the guarantee no one will change it - only transform it.
  • When Elixir sees a list of printable ASCII numbers, Elixir will print that as a charlist.
  • Whenever you see a value in IEx and you are not quite sure what it is, you can use the i/1 to retrieve information about it.
  • Keep in mind single-quoted and double-quoted representations are not equivalent in Elixir as they are represented by different types. Single quotes are charlists, double quotes are strings.
  • Elixir uses curly brackets to define tuples. Like lists, tuples can hold any value.
  • Tuples store elements contiguously in memory. This means accessing a tuple element by index or getting the tuple size is a fast operation.
  • Elixir list is like Java LinkedList and tuple is like Java array or ArrayList. This means adding to head of list is constant while adding to tail is linear.
  • Updating or adding elements to tuples is expensive because it requires creating a new tuple in memory.
  • Note that this applies only to the tuple itself, not its contents. For instance, when you update a tuple, all entries are shared between the old and the new tuple, except for the entry that has been replaced. In other words, tuples and lists in Elixir are capable of sharing their contents. This reduces the amount of memory allocation the language needs to perform and is only possible thanks to the immutable semantics of the language.
  • When counting the elements in a data structure, Elixir also abides by a simple rule: the function is named size if the operation is in constant time or length if the operation is linear.

Basic Operators

  • Elixir provides three boolean operators: or, and and not.
  • Use and, or and not when you are expecting booleans. If any of the arguments are non-boolean, use &&, || and !. For these operators, all values except false and nil will evaluate to true. Note that the output below is due to short-circuiting.
# or
iex> 1 || true
1
iex> false || 11
11

# and
iex> nil && 13
nil
iex> true && 17
17

# !
iex> !true
false
iex> !1
false
iex> !nil
true
  • The difference between == and === is that the latter is more strict when comparing integers and floats.
iex> 1 == 1.0
true
iex> 1 === 1.0
false
  • In Elixir, we can compare two different data types:
iex> 1 < :atom
true
  • The reason we can compare different data types is pragmatism. Sorting algorithms don’t need to worry about different data types in order to sort. The overall sorting order is defined below:
number < atom < reference < function < port < pid < tuple < map < list < bitstring

Pattern Matching

  • In Elixir, the = operator is actually called the match operator.
  • A variable can only be assigned on the left side of =. This is binding the value to the variable.
  • The match operator is not only used to match against simple values, but it is also useful for destructuring more complex data types.
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
  • A pattern match will error if the sides can’t be matched, for example if the tuples have different sizes. And also when comparing different types.
iex> {a, b, c} = [:hello, "world", 42]
** (MatchError) no match of right hand side value: [:hello, "world", 42]
  • The example below asserts that the left side will only match the right side when the right side is a tuple that starts with the atom :ok.
iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13

iex> {:ok, result} = {:error, :oops}
** (MatchError) no match of right hand side value: {:error, :oops}
  • A list also supports matching on its own head and 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 an existing variable’s value rather than rebinding the variable.
iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {y, ^x} = {2, 1}
{2, 1}
iex> y
2
iex> {y, ^x} = {2, 2}
** (MatchError) no match of right hand side value: {2, 2}
  • If a variable is mentioned more than once in a pattern, all references should bind to the same pattern.
iex> {x, x} = {1, 1}
{1, 1}
iex> {x, x} = {1, 2}
** (MatchError) no match of right hand side value: {1, 2}
  • In some cases, you don’t care about a particular value in a pattern. It is a common practice to bind those values to the underscore, _. For example, if only the head of the list matters to us, we can assign the tail to underscore.
iex> [h | _] = [1, 2, 3]
[1, 2, 3]
iex> h
1
  • The variable _ is special in that it can never be read from. Trying to read from it gives a compile error.
  • You cannot make function calls on the left side of a match.