Elixir – Introduction – Elixir



Elixir – Introduction – Elixir

0 1


elixir-intro

Elixir Introduction http://miguelcoba.github.io/elixir-intro

On Github miguelcoba / elixir-intro

Elixir

Introduction

Miguel Cobá / @MiguelCobaMtz

Elixir

Elixir is a dynamic, functional language designed for building scalable and maintainable applications

Erlang VM

Elixir leverages the Erlang VM

low-latency

distributed

fault-tolerant

used for web development and embedded software

Installation

http://elixir-lang.org/install.html

# Mac OS X
# Homebrew
$ brew install elixir

# Test install
$ elixir -v
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.2.1
						

Interactive Elixir IEx

$ iex
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex> 1 + 2
3
iex> "Hello" <> " world!"
"Hello world!"
iex>
						

Running scripts

# script.exs
IO.puts "Hello World!"

# Execute it in shell
$ elixir script.exs
						

Conventions

.exs for interpreted Elixir files

.ex for compiled Elixir files

Basic Types

Elixir Types

iex> 1          # integer
iex> 1.0        # float
iex> 1.0e-10    # float
iex> true       # boolean
iex> :atom      # atom / symbol
iex> "elixir"   # string
iex> [1, 2, 3]  # list
iex> {1, 2, 3}  # tuple
iex> 0b1010     # binary
iex> 0x777      # octal
iex> 0x1F       # hexadecimal
						

Arithmetic

iex> 1 + 2
3
iex> 2 * 2
4
iex> 10 / 2 # / always returns float
5.0
iex> div(10, 2) # / integer division
5
iex> div 10, 2 # parentheses are optional
5
iex> rem 10, 3 # remainder
1
						

Booleans and predicates

iex> true
true
iex> false
false
iex> true == false
false
iex> is_boolean(true)
true
iex> is_integer(1)
true
iex> is_float(1.0e-10)
true
iex> is_number(1)
true
iex> is_number(1.0)
true
						

Atoms

Atoms are constants where their name is its own value (akin to symbols in other languages)

iex> :hello
:hello
iex> :hello == :world
false
iex> :hello == "hello" # atoms are not strings
false
iex> :"hello" # but you can create atoms from strings
:hello
iex> :hello == :"hello" # but you can create atoms from strings
true
						

String

String in Elixir are encoded in UTF-8 and delimited by double quotes

iex> "Cobá ö ç ø"
"Cobá ö ç ø"
iex> "hello\nworld"  # line breaks
"hello\nworld"
iex> "hello
...> world"
"hello\nworld"
iex> IO.puts "hello\nworld" # Print string with IO.puts/1
hello
world
:ok
						

String interpolation

iex> "1 + 2 = #{1+2}"
"1 + 2 = 3"
iex> "hello #{:world}"
"hello world"
iex> "Is 1 integer? #{is_integer(1)}"
"Is 1 integer? true"
						

String concatenation

String concatenation is done with <>

iex> "foo" <> "bar"
"foobar"
						

String internals

Strings in Elixir are represented internally by binaries which are sequences of bytes

iex> is_binary("hello")
true
iex> byte_size("Cobá ö ç ø") # number of bytes in a string
14
iex> String.length("Cobá ö ç ø") # number of chars (length) in the string
10
						

Anonymous Functions

Functions are delimited by the keywords fn and end

iex> add = fn a, b -> a + b end
#Function<12.54118792/2 in :erl_eval.expr/5>
iex> is_function(add)
true
iex> is_function(add, 2)
true
iex> is_function(add, 3)
false
						

Anonymous Functions

A dot (.) between the variable and parenthesis is required to invoke an anonymous function

iex> add.(1,2)
3
						

Anonymous functions are closures: they can access variables that are in scope when the function is defined

iex> add_two = fn a -> add.(a, 2) end
#Function<6.54118792/1 in :erl_eval.expr/5>
iex> add_two.(2)
4
						

Anonymous Functions

A variable assigned inside a function does not affect its surrounding environment

iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42
						

Lists

Elixir uses square brackets to specify a list of values

Values can be of any type

Lists are Linked Lists

iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3
						

Lists

Concatenation

iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
						

Substraction

iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
						

Lists

Head

iex> hd([1, 2, 3])
1
						

Tail

iex> tl([1, 2, 3])
[2, 3]
						

Lists

  • Lists are stored in memory as linked lists, meaning that each element in a list holds its value and points to the following element until the end of the list is reached
  • We call each pair of value and pointer a cons cell
  • We can construct a cons cell with |

Lists

List construction

iex> list = [1, 2]
[1, 2]
iex> new_list = [ 0 | list ] # head and pointer to next element separated by |
[0, 1, 2]
iex> [ 1 | []]
[1]
iex> [ 1 | [2]]
[1, 2]
iex> [ 1 | [2, 3]]
[1, 2, 3]
iex> [ 1 | [2 | [3 | [4 | []]]]]
[1, 2, 3, 4]
						

Tuples

  • Elixir uses curly brackets to define tuples
  • Like lists, tuples can hold any value
  • Tuples store elements contiguously in memory
  • Accessing a tuple element per index or getting the tuple size is a fast operation
  • Tuples indexs start from zero

Tuples

iex> {:atom, "any", "value", 3, 2.0}
{:atom, "any", "value", 3, 2.0}
iex> tuple_size {:atom, "any", "value", 3, 2.0}
5
						

Tuples

Accessing elements

iex> tuple = {:hello, "world"}
{:hello, "world"}
iex> elem(tuple, 1)
"world"
						

Putting elements

iex> put_elem(tuple, 1, 3.1416) # new tuple is returned
{:hello, 3.1416}
iex> tuple # original tuple was not modified
{:hello, "world"}
						

List vs Tuples

Lists

  • Lists are stored in memory as linked lists
  • Accessing the length of a list is a linear operation
  • Updating a list is fast as long as we are prepending elements

Tuples

  • Tuples are stored contiguously in memory
  • Getting the tuple size or accessing an element by index is fast
  • Updating or adding elements to tuples is expensive (requires copying the whole tuple)

Pattern matching

The match operator

  • In Elixir, the = operator is actually called the match operator
  • The equals sign is not an assignment, is like an assertion
  • It succeeds if Elixir can find a way of making the left-hand side equal the right-hand side.

The match operator

In this case, the left-hand side is a variable and the right-hand side is an integer literal, so Elixir can make the match true by binding the variable a to value 1.

iex> a = 1
1
iex> a
1
						

The match operator

This is another match, and it passes. The variable a already has the value 1, so what’s on the left of the equals sign is the same as what’s on the right, and the match succeeds.

No new binding is performed

iex> 1 = a
1
						

The match operator

This raises an error. A variable can only be assigned on the left side of =

Elixir will only change the value of a variable on the left side of an equals sign. On the right a variable is replaced with its value

This is equivalent to 2 = 1 and causes an error

iex> 2 = a
** (MatchError) no match of right hand side value: 1
						

Pattern matching

The match operator is not only used to match against simple values, but it is also useful for destructuring more complex data types

# Pattern matching on tuples
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
						

Pattern matching

A pattern match will error in the case the sides can’t match

iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}
						

Pattern matching

A pattern match will error when comparing different types

iex> {a, b, c} = [:hello, "world", "!"]
** (MatchError) no match of right hand side value: [:hello, "world", "!"]
						

Pattern matching

But, it can match on specific values

This 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}
						

Pattern matching

Pattern matching on lists

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

Pattern matching

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]
						

The pin operator

Variables in Elixir can be rebound

iex> x = 1 # x bound to 1
1
iex> x = 2 # x rebound to 2
2
						

The pin operator

The pin operator ^ should be used 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} # this is equivalent to {y, 1} = {2, 2}, that won't match
** (MatchError) no match of right hand side value: {2, 2}
						

Underscore

If you don’t care about a particular value in a pattern, the common practice is to bind those values to the underscore, _

iex> [h|_] = [1, 2, 3]
[1, 2, 3]
iex> h
1
						

Pattern matching exercise 1

Bind the list variable to [1, 2, 3]

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

Pattern matching exercise 2

Bind the a to 1, b to 2 and c to 3 in [1, 2, 3]

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

Pattern matching exercise 3

Bind the a to 1, b to 2 and c to 3 if variable list is already bound to [1, 2, 3]

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

Pattern matching exercise 4

Bind each value to a variable, ignoring the last list, from [:atom, "string", {:some, "tuple"}, [1, 2, 3]]

iex> [atom, binary, {:some, tuple_value}, _] = [:atom, "string", {:some, "tuple"}, [1, 2, 3]]
[:atom, "string", {:some, "tuple"}, [1, 2, 3]]
iex> atom
:atom
iex> binary
"string"
iex> tuple_value
"tuple"
						

Pattern matching exercise 5

Will this match?

[a, b] = [1, [2]]
						

Yes

a bound to 1

b bound to [2]

Pattern matching exercise 6

Will this match?

[a, _b] = [1, [2]]
						

Yes

a bound to 1

_b bounded but a warning shown by compiler if used. Should be ignored

Pattern matching exercise 7

Will this match?

[:a, b] = [:"a", [2]]
						

Yes

b bound to [2]

The first element is matched literally on both sides but no binding is performed

Pattern matching exercise 8

Will this match?

{^a, 2, []} = {2, 2, []}
						

No

a bound to 1 and the pin operator was used to avoid rebinding a

iex> {^a, 2, []} = {2, 2, []}
** (MatchError) no match of right hand side value: {2, 2, []}
						

Control-flow structures

case

Allows us to compare a value against many patterns until we find a matching one

Execute clause body corresponding to the first clause that matches

If no clause matches, an error is raised.

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"
						

case

Clauses also allow extra conditions to be specified via guards

iex> case {1, 2, 3} do
...>   {1, x, 3} when x > 0 ->
...>     "Will match"
...>   _ ->
...>     "Would match, if guard condition were not satisfied"
...> end
"Will match"
						

cond

Evaluates the expression corresponding to the first clause that evaluates to a truthy value

Raises an error if all conditions evaluate to nil or false

iex> cond do
...>   1 + 1 == 1 ->
...>     "This will never match"
...>   2 * 2 != 4 ->
...>     "Nor this"
...>   true ->
...>     "This will"
...> end
						

if/unless

if and unless are macros in Elixir

They expect the first argument to be a condition and the second argument to be a keyword list

iex> if(true, do: "hello world")
"hello world"
iex> unless(true, do: "hello world")
nil
						

if/unless

iex> if(true, do: "hello", else: "bye")
"hello"
iex> unless(true, do: "hello", else: "bye")
"bye"
						

if/unless

It's also possible to pass a block to the if/else macros

iex> if true do
...>   "hello"
...> end
"hello"
iex> unless true do
...>   "hello"
...> end
nil
						

if/unless

iex> if true do
...>   "hello"
...> else
...>   "world"
...> end
"hello"
iex> unless true do
...>   "hello"
...> else
...>   "bye"
...> end
"bye"
						

Keyword lists

A keyword list is a list of tuples where the first item of each tuple (i.e. the key) is an atom

iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]  # Syntactic sugar
true
iex> list[:a]
1
						

Keyword lists

Adding values to a keyword list

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

Keyword lists

Values added to the front are the ones fetched on lookup

iex> new_list = [a: 0] ++ list # another tuple with same key added to front
[a: 0, a: 1, b: 2]
iex> new_list[:a]
0
						

Maps

A map is a collection of key/value pairs

A map is created using the %{}

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
						

Maps and pattern matching

When a map is used in a pattern, it will always match on a subset of the given value

A map matches as long as the keys in the pattern exist in the given map. Therefore, an empty map matches all maps.

iex> %{} = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
						

Maps and variables

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
iex> map[1]
:one
iex> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three} # will match
%{1 => :one, 2 => :two, 3 => :three}
						

The Map Module

Convenience functions to manipulate maps

iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
						

Maps and keyword syntax

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}
						

Maps and dot syntax

Maps is that they provide their own syntax for updating and accessing atom keys

iex> map = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> map.a
1
iex> map.c
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
iex> %{map | :a => 2} # updating the map
%{:a => 2, 2 => :b}
iex> %{map | :c => 3} # key must exist before updating it
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
						

Modules

Modules

We group several functions into modules

We use the defmodule macro to define a new module

We use the def macro to define a function inside a module

Modules

iex> defmodule Math do
...>   def sum(a, b) do
...>     a + b
...>   end
...> end

iex> Math.sum(1, 2)
3
						

Modules

We use the defp macro to define a private function inside a module

A function defined with def/2 can be invoked from other modules while a private function can only be invoked locally

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)
						

Guards and multiple guards in functions

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_number(x) do # another declaration of zero? function with a guard
    false
  end
end

IO.puts Math.zero?(0)       #=> true
IO.puts Math.zero?(1)       #=> false
IO.puts Math.zero?([1,2,3]) #=> ** (FunctionClauseError)
						

Functions and block syntax

Similar to constructs like if, named functions support both do: and do/end block syntax

do/end is just a convenient syntax for the keyword list format

defmodule Math do
  def zero?(0), do: true
  def zero?(x) when is_number(x), do: false
end
						

Function capturing

The notation name/arity to refer to functions can be used to retrieve a named function as a function type

iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function(fun)
true
iex> fun.(0)
true
						

Function capturing

Local or imported functions, like is_function/1, can be captured without the module

iex> fun2 = &is_function/1
&:erlang.is_function/1
iex> fun2.(fun)
true
iex> fun2.(fun2)
true
						

Function capturing

Note the capture syntax can also be used as a shortcut for creating functions

The &n represents the nth argument passed into the function

iex> fun3 = &(&1 + 1)
#Function<6.54118792/1 in :erl_eval.expr/5>
iex> fun3.(1)
2
						

&(&1+1) above is exactly the same as fn x -> x + 1 end

Function capturing

Capture syntax can be used for module functions, too

iex(3)> fun4 = &Kernel.max(-&1, -&2)
#Function<12.54118792/2 in :erl_eval.expr/5>
iex(4)> Kernel.max(1, 2)
2
iex(5)> fun4.(1, 2)
-1
						

Default arguments

Elixir support default arguments

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
						

Recursion

Loops

Functional languages rely on recursion to do loops: a function is called recursively until a condition is reached that stops the recursive action from continuing. No data is mutated in this process

defmodule Recursion do
  def print_multiple_times(msg, n) when n <= 1 do
    IO.puts msg
  end

  def print_multiple_times(msg, n) do
    IO.puts msg
    print_multiple_times(msg, n - 1)
  end
end

Recursion.print_multiple_times("Hello!", 3)
# Hello!
# Hello!
# Hello!
						

Recursion example 1

Map and reduce

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
						

Functions exercise 1

Write a module with a function to calculate the factorial of n

defmodule Math do
	def factorial(n), do: do_factorial(n, 1)

	#defp do_factorial(1, acc), do: acc
	defp do_factorial(n, acc) when n == 1, do: acc
	defp do_factorial(n, acc), do: do_factorial(n - 1, acc * n)
end

IO.puts Math.factorial(50)
# 30414093201713378043612608166064768844377641568960512000000000000
						

The Enum module

The Enum module

The Enum module provides a huge range of functions to transform, sort, group, filter and retrieve items from enumerable

iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]
iex> Enum.map(1..3, fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.reduce(1..3, 0, &+/2)
6
						

Eager vs lazy

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]
						

Eager vs lazy

When performing multiple operations with Enum, each operation generates an intermediate list

iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000
						

The pipe operator |>

It takes the output from the expression on its left side and passes it as the first argument to the function call on its right side

iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
7500000000
						

The Stream module

Elixir provides the Stream module which supports lazy operations

Streams are useful when working with large, possibly infinite, collections

iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
7500000000
						

Slides

http://miguelcoba.github.io/elixir-intro

Thank you!

Miguel Cobá

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Elixir Introduction Miguel Cobá / @MiguelCobaMtz