On Github miguelcoba / elixir-intro
Elixir is a dynamic, functional language designed for building scalable and maintainable applications
Elixir leverages the Erlang VM
low-latency
distributed
fault-tolerant
used for web development and embedded software
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
$ 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>
# script.exs IO.puts "Hello World!" # Execute it in shell $ elixir script.exs
.exs for interpreted Elixir files
.ex for compiled Elixir files
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
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
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 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 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
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 is done with <>
iex> "foo" <> "bar" "foobar"
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
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
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
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
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
iex> [1, 2, 3] ++ [4, 5, 6] [1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false] [1, 2, 3, true]
iex> hd([1, 2, 3]) 1
iex> tl([1, 2, 3]) [2, 3]
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]
iex> {:atom, "any", "value", 3, 2.0} {:atom, "any", "value", 3, 2.0} iex> tuple_size {:atom, "any", "value", 3, 2.0} 5
iex> tuple = {:hello, "world"} {:hello, "world"} iex> elem(tuple, 1) "world"
iex> put_elem(tuple, 1, 3.1416) # new tuple is returned {:hello, 3.1416} iex> tuple # original tuple was not modified {:hello, "world"}
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
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
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
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"
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"}
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", "!"]
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 on lists
iex> [a, b, c] = [1, 2, 3] [1, 2, 3] iex> a 1
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]
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 ^ 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}
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
Bind the list variable to [1, 2, 3]
iex> list = [1, 2, 3] [1, 2, 3] iex> list [1, 2, 3]
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
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
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"
Will this match?
[a, b] = [1, [2]]
Yes
a bound to 1
b bound to [2]
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
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
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, []}
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"
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"
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 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
iex> if(true, do: "hello", else: "bye") "hello" iex> unless(true, do: "hello", else: "bye") "bye"
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
iex> if true do ...> "hello" ...> else ...> "world" ...> end "hello" iex> unless true do ...> "hello" ...> else ...> "bye" ...> end "bye"
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
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]
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
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
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}
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}
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}]
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 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}
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
iex> defmodule Math do ...> def sum(a, b) do ...> a + b ...> end ...> end iex> Math.sum(1, 2) 3
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)
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)
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
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
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
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
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
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
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!
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
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 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
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]
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
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
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
Miguel Cobá
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.