Elixir Quick Reference
A quick reference for the Elixir programming Language and standard library.
Elixir Website: http://elixir-lang.org/
Elixir Documentation: http://elixir-lang.org/docs.html
Elixir Source: https://github.com/elixir-lang/elixir
Try Elixir Online: https://glot.io/new/elixir
Elixir Regex editor/testor: http://www.elixre.uk/
This Document: https://github.com/itsgreggreg/elixir_quick_reference
Table of Contents
- Basic Types
- Collection Types
- Syntax
- Truthiness
- Sorting
- Modules
- Errors
- Control Flow
- Guards
- Anonymous Functions
- Comprehensions
- Sigils
- Metaprogramming
- Processes
- Structs
- Working with Files
- Erlang Interoperability
- IEx
- Mix
- Tests
- Style Guide
Basic Types
Integer
Can be specified in base 10, hex, or binary. All are stored as base 10.
> 1234567 == 1_234_567 # true
> 0xcafe == 0xCAFE # true
> 0b10101 == 65793 # true
> 0xCafe == 0b1100101011111110 # true
> 0xCafe == 51_966 # true
> Integer.to_string(51_966, 16) == "CAFE" # true
> Integer.to_string(0xcafe) == "51996" # true
Float
64bit double precision. Can be specified with an exponent. Cannot begin or end with .
.
> 1.2345
> 0.001 == 1.0e-3 # true
> .001 # syntax error!
Atom
Constants whose name is their value.
Are named in this format:
To use other characters you must quote the atom.
TODO: Note which characters can be used when quoted.
Stored in a global table once used and never de-allocated so avoid programmatic creation.
> :something
> :_some_thing
> :allowed?
> :Some@Thing@12345
> :"รรฑรฎรงรธdรฉ and Spaces"
> Atom.to_string(:Yay!) # "Yay!"
> :123 # syntax error!
Boolean
true
and false
are just syntactic sugar for :true
and :false
and not a special type.
> true == :true # true
> false == :false # true
> is_boolean(:true) # true
> is_atom(false) # true
> is_boolean(:True) # false!
Nil
nil
is syntactic sugar for :nil
and is not a special type.
> nil == :nil # true
> is_atom(nil) # true
Binary
A binary is a sequence of bytes enclosed in << >>
and separated with ,
.
By default each number is 8 bits though size can be specified with:
::size(n)
, ::n
, ::utf8
, ::utf16
, ::utf32
or ::float
If the number of bits in a binary is not divisible by 8, it is considered a bitstring.
Binaries are concatenated with <>
.
> <<0,1,2,3>>
> <<100>> == <<100::size(8)>> # true
> <<4::float>> == <<64, 16, 0, 0, 0, 0, 0, 0>> # true
> <<65::utf32>> == <<0, 0, 0, 65>> # true
> <<0::2, 1::2>> == <<1::4>> # true
> <<1,2>> <> <<3,4>> == <<1,2,3,4>> # true
> is_binary(<<1,2,3,4>>) # true
> is_binary(<<1::size(4)>>) # false!, num of bits not devisible by 8
> is_bitstring(<<1::size(4)>>) # true
String
Strings are UTF-8 encoded binaries. They are enclosed in double quotes("
).
They can span multiple lines and contain interpolations.
Interpolations are enclosed in #{}
and can contain any expression.
Strings, being binaries, are concatenated with <>
.
> "This is a string."
> "โโ
โโปโโฏโญโขโฌโโโซโโงโฎโปโโโโโโโโฆโ โฃโฅโฆโโ" # no problem :)
> "This is an #{ Atom.to_string(:interpolated) } string."
> "Where is " <> "my other half?"
> "multi\nline" == "multi
line" # true
> <<69,108,105,120,105,114>> == "Elixir" # true
> String.length("๐ฉ") # 1
> byte_size("๐ฉ") # 4
> is_binary("any string") # true
> String.valid?("ใใใซใกใฏ") # true
> String.valid?("hello" <> <<255>>) # false!
> String.valid?(<<4>>) # true
> String.printable?(<<4>>) # false! 4 is a valid UTF-8 codepoint, but is not printable.
Escape Sequences
characters | whitespace | control sequences |
---|---|---|
\" โ double quote |
\b โ backspace |
\a โ bell/alert |
\' โ single quote |
\f - form feed |
\d - delete |
\\ โ single backslash |
\n โ newline |
\e - escape |
| `\s` โ space | `\r` โ carriage return
| `\t` - tab | `\0` - null byte
| `\v` โ vertical tab |
\x...
- character with hexadecimal representation.
\x{...}
- character with hexadecimal representation with one or more hexadecimal digits.
> "\x3f" == "?" # true
> "\x{266B}" == "โซ" # true
> "\x{2660}" == "โ " # true
Regular Expression
Inherited from Erlang's re
module and are Perl compatible.
Written literally with the ~r
Sigil and can span multiple lines.
Can have a number of modifiers specified directly after the pattern.
Many functions take a captures option that limits captures.
Modifiers:
u
enables unicode specific patterns like \p and changes escapes like \w, \W, \s and friends to also match on unicode. It expects valid unicode strings to be given on matchi
ignore cases
dot matches newlines and also set newline to anycrlf.m
^ and $ match the start and end of each line; use \A and \z to match the end or start of the stringx
whitespace characters are ignored except when escaped and#
delimits commentsf
forces the unanchored pattern to match before or at the first newline, though the matched text may continue over the newline r - inverts the โgreedinessโ of the regexp
To override newline treatment start the pattern with:
(*CR)
carriage return(*LF)
line feed(*CRLF)
carriage return, followed by linefeed(*ANYCRLF)
any of the three above(*ANY)
all Unicode newline sequences
> Regex.compile!("caf[eรฉ]") == ~r/caf[eรฉ]/ # true
> Regex.match?(~r/caf[eรฉ]/, "cafรฉ") # true
> Regex.regex?(~r"caf[eรฉ]") # true
> Regex.regex?("caf[eรฉ]") # false! string not compiled regex
> Regex.run(~r/hat: (.*)/, "hat: ๐ฉ", [capture: :all_but_first]) == ["๐ฉ"] # true
# Modifiers
> Regex.match?(~r/mr. bojangles/i, "Mr. Bojangles") # true
> Regex.compile!("mr. bojangles", "sxi") # ~r/mr. bojangles/sxi
# Newline overrides
> ~r/(*ANY)some\npattern/
Collection Types
List
Simple linked lists that can be of any size and can have elements of any type.
They are enclosed in [ ]
and elements are comma separated.
Concatenated with ++
and subtracted with --
.
Can be constructed with the cons operator |
.
Best for sequential access, fastest when elements are added and subtracted from the head.
Instead of building a list by adding to its tail, add to the head and reverse the list.
List implements the enumerable protocol so we use Enum for many common operations.
> [1, 2, 3.4, "a", "b", :c, [:d]]
> [ 1 | [2 | [3]]] == [1, 2, 3] # true
> [1, 2, 3.4] ++ ["a", "b", :c] # [1, 2, 3.4, "a", "b", :c]
> [1, 2, 3.4, "a", "b", :c, [:d]] -- [2, "a", "c"] # [1, 3.4, "b", :c, [:d]]
> hd [1, 2, 3] # 1
> tl [1, 2, 3] # [2, 3]
> length [:a, :b, :c, :d] # 4
> Enum.reverse [:a, :b, :c] # [:c, :b, :a]
> Enum.member? [:a, :b], :b # true
> Enum.join [:a, :b], "_" # "a_b"
> Enum.at [:a, :b, :c], 1 # :b
Charlist
A List of UTF-8 codepoints.
Other than syntax they are exactly the same as Lists and are not a unique class.
Can span multiple lines and are delimited with single quotes '
.
Have the same Escape Sequences as String.
> 'char list'
> [108, 105, 115, 116] == 'list' # true
> 'turbo' ++ 'pogo' # 'turbopogo'
> 'char list' -- 'a l' # 'christ'
> hd 'such list' == ?s # true
> String.to_char_list "tacosalad" # 'tacosalad'
> List.to_string 'frijoles' # "frijoles"
> [?Y, ?e, ?a, ?h] == 'Yeah' # true
Tuple
Can be of any size and have elements of any type.
Elements are stored contiguously in memory.
Enclosed in { }
and elements are comma separated.
Fast for index-based access, slow for a large number of elements.
> { :a, 1, {:b}, [2]}
> put_elem({:a}, 0, :b) # {:b}
> elem({:a, :b, :c}, 1) # b
> Tuple.delete_at({:a, :b, :c}, 1) # {:a, :c}
> Tuple.insert_at({:a, :c}, 1, :b) # {:a, :b, :c}
> Tuple.to_list({:a, :b, :c}) # [:a, :b, :c]
Keyword List
A List of 2 element Tuples where each Tuple's first element is an Atom.
This atom is refered to as the keyword, or key.
Have a special concice syntax that omits the Tuple's brackets and places the key's colon on the right.
Being Lists they:
- are order as specified
- can have duplicate elements and multiple elements with the same key
- are fastest when accessed at the head.
- are concatenated with
++
and subtracted with--
.
Elements can be accessed with [:key]
notation. The first Element with a matching :key
will be returned.
2 Keyword Lists are only equal if all elements are equal and in the same order.
# Full Syntax
> [{:a, "one"}, {:b, 2}]
# Concice Syntax
> [a: "one", b: 2]
> [a: 1] ++ [a: 2, b: 3] == [a: 1, a: 2, b: 3] # true
> [a: 1, b: 2] == [b: 2, a: 1] # false! elements are in different order
> [a: 1, a: 2][:a] == 1 # true
> Keyword.keys([a: 1, b: 2]) # [:a, :b]
> Keyword.get_values([a: 1, a: 2], :a) # [1, 2]
> Keyword.keyword?([{:a,1}, {:b,2}]) # true
> Keyword.keyword?([{:a,1}, {"b",2}]) # false! "b" is not an Atom
> Keyword.delete([a: 1, b: 2], :a) # [b: 2]
Map
Key - Value store where Keys and Values are of any type.
Cannot have multiple values for the same key and are unordered.
Map
s are enclosed in %{ }
, elements are comma seperated, and elemets have the form: key =>
value.
If all keys are Atom
s, the =>
can be omitted and the Atom
's :
must be on the right.
Values are accessed with [key]
notation.
Maps can be accessed with .key
notation if key is an Atom
.
Maps can be updated by enclosing them in %{}
and using the cons |
operator.
Maps can be of any size and are fastest for key based lookup.
> %{:a => 1, 1 => ["list"], [2,3,4] => {"a", "b"}}
> %{:a => 1, :b => 2} == %{a: 1, b: 2} # true
> %{a: "one", b: "two", a: 1} == %{a: 1, b: "two"} # true
> %{a: "one", b: "two"} == %{b: "two", a: "one"} # true
> %{a: "one", b: "two"}[:b] # "two"
> %{a: "one", b: "two"}.b # "two"
> %{a: "one", a: 1} == %{a: 1} # true
> %{:a => "one", "a" => "two"}."a" == "two" # false! watchout
> Map.keys( %{a: 1, b: 2} ) == [:a, :b] # true
> %{ %{a: 1, b: 2, c: 3} | :a => 4, b: 5 } # %{a: 4, b: 5, c: 3}
> Map.merge( %{a: 1, b: 2}, %{a: 4, c: 3} ) # %{a: 4, b: 2, c: 3}
> Map.put( %{a: 1}, :b, 2 ) == %{a: 1, b: 2} # true
> Kernel.get_in # TODO
> Kernel.put_in # TODO
Struct
Structs can be thought of as bare Maps with pre-defined keys, default values and where the keys must be atoms.
Structs are defined at the top level of a Module and take the Module's name.
Structs do not implement the Access or Enumerable protocol and can be considered bare Maps.
Structs have a special field called __struct__
that holds the name of the struct.
defmodule City do
defstruct name: "New Orleans", founded: 1718
end
nola = %City{}
chi = %City{name: "Chicago", founded: 1833}
nola.name # "New Orleans"
chi.founded # 1833
nola.__struct__ # City
Range
Used to specify the first and last elements of something.
Just a Struct of type Range with a first
field and a last
field.
Have a special ..
creation syntax but can also be created like any other struct.
> a = 5..10
> b = Range.new(5, 10)
> c = %Range{first: 5, last: 10}
> Range.range?(c) # true
> Enum.each(5..10, fn(n) -> n*n end) # prints all the squares of 5..10
> Map.keys(5..10) # [:__struct__, :first, :last]
> (5..10).first # 5
Streams
Lazy enumerables.
Are created out of enumerables with functions in the Stream
module.
Elements are not computed until a method from the Enum
module is called on them.
> a = Stream.cycle 'abc'
#Function<47.29647706/2 in Stream.unfold/2> # Infinate Stream created
> Enum.take a, 10 # Enum.take computes the 10 elements
'abcabcabca'
With Stream.unfold/2 you can create an arbitrary stream.
> s = Stream.unfold( 5,
fn 0 -> nil # returning nil halts the stream
n -> {n, n-1} # return format {next-val, rest}
end)
> Enum.to_list(s)
[5, 4, 3, 2, 1]
Syntax
Variables
Are declared and initialized upon use.
Are named in the following format:
Can hold any data structure and can be assigned more than once.
> something = :anything
> something = ["a", "list", "of", "strings"]
> _yeeHaw1234! = %{:a => :b}
Operators
Standard infix
- Equality
==
,!=
- Strict equality
===
and!==
do not coerce Floats to Integers.1 === 1.0 #false
- Comparison
>
,<
,>=
,<=
- Logic, short-circuiting
&&
and||
- Boolean only Logic, short-circuiting
and
andor
. (Only left side must be boolean) - Math
+
,-
,*
,/
Standard prefix
- Negation, any type
!
,!1 == false
- Negation, boolean only
not
,not is_atom(5) == true
= (match)
left =
right
Performs a Pattern Match.
^ (pin)
Used to pin the value of a variable in the left side of a Pattern Match.
a = "thirty hams"
{b, ^a} = {:i_need, "thirty hams"} # `b` is set to `:i_need`
{^a, {^a}} = {"thirty hams", {"thirty hams"}} # nothing is set, but the match succedes
|> (pipe)
|>
Takes the result of a statement on its left and passes as the first argument to the function on its right.
The statement on the left can be on the preceeding line of code.
> [1,2,3] |> hd |> Integer.to_string |> IO.inspect # "1"
# โฃ doesn't work in iex
hd([1,2,3])
|> Integer.to_string
|> IO.inspect # "1"
=~ (string match)
=~
Takes a string on the left and on the right either a string or a regular expression.
If the string on the right is a substring of left, true
is returned.
If the regular expression on the right matches the string on the left, true
is returned.
Otherwise false
is returned.
> "abcd" =~ ~r/c(d)/ # true
> "abcd" =~ ~r/e/ # false
> "abcd" =~ "bc" # true
> "abcd" =~ "ad" # false
? (codepoint)
?
Returns the UTF-8 codepoint of the character immediately to its right.
Can only take one character, accepts Escape Sequences.
Remember Charlists are just lists of UTF-8 codepoints.
> ?a # 97
> ?โซ # 9835
> ?\s # 32
> ?? # 63
> [?โ, ?!] == 'โ!' # true
& (capture)
- TODO
Ternary
Elixir has no ternary operator. The same effect though can be achieved with the if
macro.
> a = if true, do: "True!", else: "False!"
> a == "True!" # true
in
left in
right.
Used to check if the enumerable on the right contains the data structure on the left.
Right hand side must implement the Enumerable Protocol.
> :b in [:a, :b, :c] # true
> [:c] in [1,3,[:c]] # true
> :ok in {:ok} # ERROR: protocol Enumerable not implemented for {:ok}
Comments
#
indicates that itself and anything after it until a new line is a comment. That is all.
Semicolons
Semicolons can be used to terminate statements but in practice are rarely if ever used.
The only required usage is to put more than one statement on the same line. a = 1; b = 2
This is considered bad style and placing them on seperate lines is much prefered.
Do, End
Blocks of code passed to macros start with do
and end with end
.
if true do
"True!"
end
if true do "True!" end
# inside a module
def somefunc() do
IO.puts "multi line"
end
if true do
"True!"
else
"False!"
end
You can pass the block as a single line and without end
with some extra puctuation.
# โฃ โฃ โฃ no end keyword
if true, do: "True!"
# โฃ โฃ โฃ โฃ โฃ no end keyword
if true, do: "True", else: "False!"
# inside a module
# โฃ โฃ โฃ no end keyword
def someFunc(), do: IO.puts "look ma, one line!"
Syntactic sugar for
if(true, [{:do, "True!"}, {:else, "False!"}])
def(someFunc(), [{:do, IO.puts "look ma, one line!"}])
Pattern Matching
A match has 2 main parts, a left side and a right side.
# โLeft โRight
# โโโโโดโโโโ โโโโโดโโโโโโโ
{:one, x} = {:one, :two}
# โRight
# โโโโโดโโโโโโโ
case {:one, :two} do
# โLeft
# โโโโโดโโโโ
{:one, x} -> IO.puts x
# โLeft
_ -> IO.puts "no other match"
end
The right side is a data structure of any kind.
The left side attempts to match itself to the data structure on the right and bind any variables to substructures.
The simplest match has a lone variable on the left and will match anything:
# in these examples `x` will be set to whatever is on the right
x = 1
x = [1,2,3,4]
x = {:any, "structure", %{:whatso => :ever}}
But you can place the variables inside a structure so you can capture a substructure:
# `x` gets set to only the `substructure` it matches
{:one, x} = {:one, :two} # `x` is set to `:two`
[1,2,n,4] = [1,2,3,4] # `n` is set to `3`
[:one, p] = [:one, {:apple, :orange}] # `p` is set to `{:apple, :orange}`
There is also a special _
variable that works exactly like other variables but tells elixir, "Make sure something is here, but I don't care exactly what it is.":
# in all of these examples, `x` gets set to `:two`
{_, x} = {:one, :two}
{_, x} = {:three, :two}
[_,_,x,_] = [1,{2},:two,3]
If you place a variable on the right, its value is used:
# โSame as writing {"twenty hams"}
a = {"twenty hams"} โฃ
{:i_have, {b}} = {:i_have, a} # `b` is set to "twenty hams"
In the previous example you are telling elixir: I want to match a structure that is a tuple, and this tuple's first element is going to be the atom :i_have. This tuple's second element is going to be a tuple. This second tuple is going to have one element and whatever it is I want you to bind it to the variable b.
If you want to use the value of a variable in your structure on the left you use the ^
operator:
a = "thirty hams"
{b, ^a} = {:i_need, "thirty hams"} # `b` is set to `:i_need`
{^a, {^a}} = {"thirty hams", {"thirty hams"}} # nothing is set, but the match succedes
Maps
Individual keys can be matched in Maps like so:
nola = %{ name: "New Orleans", founded: 1718 }
%{name: city_name} = nola # city_name now equals "New Orleans"
%{name: _, founded: city_founded} = nola # Map must have both a name and a founded key
You can use the pin operator (^) to match on variables:
field = "founded"
%{^field: city_founded} = nola # city_founded now equals 1718
Binaries
<< size::8, rest::binary>> = <<3,0,25,1,1,2,1,6,4,3>>
<< data::size(size)-unit(16)-binary, rest::binary>> = rest
- TODO
Ranges
Ranges can be pattern matched if both their values are integers.
min..max = 20..5000
min == 20 # true
max == 5000 # true
min..max == 1..10.0 # Thats an Argument Error
Reserved words
These words are reserved for the language and cannot be used as variables, module or method names.
nil
, true
, false
, __MODULE__
,__FILE__
,__DIR__
,__ENV__
,__CALLER__
Truthiness
:nil
and :false
are falsy.
nil
and false
are syntactic sugar for :nil
and :false
.
Everything else is truthy.
> !!nil # false
> !!0 # true
> nil || :nil || false || :false || 1 # 1
Sorting
Any types can be compared using comparison operators.
number < atom < reference < functions < port < pid < tuple < maps < list < bitstring
10000 < :five == true
"something" > &div/2 == true
Modules
Modules organize code under a namespace.
They can be meta-programmed but are defined at compile time and cannot be changed after, only replaced .
Named according to the following format:
Declaration
defmodule MyModule do
end
Module Functions
Names must begin with a-z
.
Names can contain a-Z
, A-Z
, 0-9
and _
.
May end with ?
or !
.
defmodule MyModule do
def my_function do
IO.puts("Hello from my function")
end
end
def
is actually a macro, and like calling any macro, do ... end can be written as a one liner:
defmodule MyModule do
def my_function, do: IO.puts("Hello from my function")
end
Inside of the defining module, functions may be called by name. Outside they must be called with the defining Module's name and a .
. Eg: IO.puts()
defmodule MyModule do
def function1 do
IO.puts "func 1"
end
def function2 do
function1
IO.puts "funct 2"
end
end
> MyModule.function2
Arguments are passed to functions positionally and can have default arguments.
Arguments can be of any Type.
defmodule MyModule do
# โฃ indicates "earthlings" to be the default for who
def greet(greeting, who \\ "earthlings") do
IO.puts("#{greeting} #{who}")
end
end
> MyModule.greet("'sup", "y'all?") # "'sup y'all?"
> MyModule.greet("greetings") # "greetings earthlings"
Module functions can be defined multiple times to support different configurations of arguments.
defmodule MyModule do
def greet() do
greet("hello", "you")
end
def greet(greeting, who) do
IO.puts("#{greeting} #{who}")
end
end
> MyModule.printer("hello") # "hello"
> MyModule.printer([1,2,3]) # [1,2,3]
> MyModule.printer() # "nothing passed"
They can also be defined multiple times to Pattern Match on arguments passed.
def is_it_the_number_2?(2) do
true
end
def is_it_the_number_2(value) do
false
end
You can ignore arguments with _
and our previous example is better written as
def is_it_the_number_2?(2) do
true
end
# โฃ underscore ignores argument
def is_it_the_number_2(_) do
false
end
Module function definitions can have Guards.
def square(n) when is_number(n), do: n * n
def square(_), do: raise "not a number"
Private Functions
To make a function private to a module use defp
instead of def
.
defmodule ModA do
defp hi, do: IO.puts "Hello from ModA"
def say_hi, do: hi
end
ModA.say_hi
# Hello from ModA
ModA.hi
# ** (UndefinedFunctionError) undefined function ModA.hi/0 ModA.hi()
Working with other modules
Inside of a module you can use one of the 4 directives to interact with other modules.
import
import SomeModule
brings all modules and macros of SomeModule into the enclosing module so you can use them un-namespaced
import
can take either an only:
or except:
list in which you specify functions and macros to include.
Alternatively import SomeModule, only:
can take :functions
or :macros
to specify only those.
def ModA do
import ModB # All Functions and Macros in ModB
import ModB, except: [destroy_planet: 1] # All Functions and Macros except destroy_planet/1
import ModB, only: :functions # All functions, no macros
import ModB, only: [say_hi: 0, fibonacci: 1] # Only the specified functions or macros
end
require
require SomeModule
allows you to use macros of SomeModule. It also makes sure that SomeModule is compiled before the enclosing module.
use
use SomeModule
first requires SomeModule and then calls the macro SomeModule.__using__. It is often used to perform setup for metaprogramming.
alias
alias SomeVery.Long.ModuleName, as: SVLMN
is used simply to shorten a module name to cut down on typing.
Attributes
Pieces of data that can be thought of as metadata or constants for a module.
They are inlined by the compiler and cannot be changed at runtime.
They can be set multiple times and the value used will be the value set when the function is defined.
defmodule ModA do
@name "April"
def first, do: @name
@name "O'Neal"
def last, do: @name
end
TODO:
- @external_resource
- Better explanation of attributes in relation to metaprogramming
Documentation
Elixir has documentation built in and you can document your modules and functions with Attributes.
@moduledoc
describes your module.
@doc
describes module functions.
defmodule MathUtils do
@moduledoc """
Random math related functions
"""
@doc "Squares the given number."
def square(n), do: n*n
end
Introspection
__info__(:functions)
Errors
Only to be used in exceptional circumstances, not for control flow.
Built in errors are listed in the docs: http://elixir-lang.org/docs/stable/elixir/ArgumentError.html . They're on the left.
Raise
raise
a runtime error:
> raise "not fabulous enough"
** (RuntimeError) not fabulous enough
raise
a different error:
> raise ArgumentError, "I'm done. We're talking in circles."
#** (ArgumentError) I'm done. We're talking in circles.
Some errors take specific options, and you must read the source to find them:
> raise KeyError, key: "to car", term: "pocket"
#** (KeyError) key "to car" not found in: "pocket"
Custom Error
defmodule LandWarWithRussiaError do
defexception message: "Never."
end
> raise LandWarWithRussiaError
#** (LandWarWithRussiaError) Never.
Rescue
try do
if false do
raise "are we there yet?"
else
raise ArgumentError, "I'll pull this car around!"
end
rescue
e in RuntimeError -> IO.puts "No!"
e in ArgumentError -> IO.puts "That's it we're going home!"
end
Remember There are much better ways to control flow in Elixir than raising/rescuing errors.
Errors should be reserved for truly exceptional situations.
Control Flow
if/unless
if :something_truthy do
IO.puts "something truthy happened"
else
IO.puts "false or nil happened"
end
unless :something_truthy do
IO.puts "nil or false happened"
else
IO.puts "something truthy happened"
end
case
case
let's you pattern match on a value.
If no cases match it throws a MatchError
.
case 137 do
"137" -> IO.puts "I require 137 the number."
137 -> IO.puts "Ahh much better."
138 ->
IO.puts "Blocks can start on the next line as well."
end
Like all pattern matches, _
will match anything and can be used as a catchall:
case {:ok, "everything went to plan"} do
{:ok, message} -> IO.puts message
{:error, message} -> IO.puts "ERROR!: #{message}"
# โฃcatchall, otherwise you'll get an error if nothing matches
_ -> IO.puts "I match everything else!"
end
You can have guards on your cases:
case 1_349 do
n when is_integer n -> IO.puts "you gave me an integer"
n when is_binary n -> IO.puts "you gave me a binary"
_ -> IO.puts "you gave me neither an integer nor binary"
end
cond
cond
takes one or more conditions and runs the first truthy one it finds.
Often used where imperative languages would use elseif.
If no statements evaluate to true it throws a MatchError
.
cond do
false -> IO.puts "I will never run"
true -> IO.puts "I will always run"
1235 -> IO.puts "I would run if that dang true wasn't on top of me."
end
true
is often used as a catch all:
guess = 12
cond do
guess == 10 -> IO.puts "You guessed 10!"
guess == 46 -> IO.puts "You guessed 46!"
true ->
IO.puts "I give up."
end
throw/catch
Inside of a try
block you can throw
any data type and pattern match on it inside a catch
block.
catch
blocks work the same as case blocks.
after
blocks will always run, throw or no throw.
try do
IO.puts "Inside a try block"
throw [:hey, "Reggie"]
IO.puts "if there is a throw before me, I'll never run."
catch
x when is_number(x) -> IO.puts "!!A number was thrown."
[:hey, name] -> IO.puts "!!Hey was thrown to #{name}."
_ -> IO.puts "Something else was thrown."
after
IO.puts "I run regardless of a throw."
end
with
Takes a series of pattern matches, runs them in order, if all pattern matches succeed it returns its do
block. If a pattern match doesn't succeed, the non-matching value is returned and with
is exited.
You can supply an else
block that essentially functions as a case
block for an uncussessful match.
Variables assigned in a with
block do not leak into outer scope.
A comma must come after every match.
nums = [8,13,44]
# โleft arrow โcomma
# match left | match right |
# โโโโโดโโโโโ โฃ โโโโโโโโโดโโโโโโโโโโโฃ
with {:ok, num} <- Enum.fetch(nums, 2),
"44" <- Integer.to_string(num),
do: "it was 44"
# Paterns can take guards
with a when is_nil(a) <- nil,
do: "Accepts guards"
else
_ -> "Does not accept guards"
# From the docs
opts = %{width: 10, height: 15}
with {:ok, width} <- Map.fetch(opts, :width),
{:ok, height} <- Map.fetch(opts, :height),
do: {:ok, width * height}
# returns {:ok, 150}
opts = %{width: 10}
with {:ok, width} <- Map.fetch(opts, :width),
{:ok, height} <- Map.fetch(opts, :height),
do: {:ok, width * height}
# returns :error as that's what Map.fetch returns when a key is not present.
# โโ Or you can catch the error in an else block
else
:error -> "A key wasn't found!"
Guards
TODO: how to use guards.
Errors generated in guards are swallowed and simply cause the guard to fail.
Functions and operators allowed in guard clauses:
= | logic | math | type_checking | map | tuple | list | binary | Kernel |
---|---|---|---|---|---|---|---|---|
== | or | + | is_atom | map_size() | elem() | a in b |
bit_size() | node() |
!= | and | - | is_binary | tuple_size() | hd() | byte_size() | self() | |
=== | not | * | is_bitstring | tl() | ||||
!== | / | is_boolean | length() | |||||
> | abs() | is_exception | ||||||
< | div() | is_float | ||||||
<= | float() | is_function | ||||||
>= | rem() | is_integer | ||||||
round() | is_nil | |||||||
trunc() | is_list | |||||||
is_number | ||||||||
is_pid | ||||||||
is_port | ||||||||
is_reference | ||||||||
is_tuple |
Notice !
, &&
and ||
are not allowed in guard clauses.
Anonymous Functions
Closures in which the captured variables are set at definition.
Are essentially case blocks you can pass around.
Take the general form:
fn (1, 2, 3) -> IO.inspect 1*2*3
(a, b, c) -> IO.inspect [a,b,c]
end
Are called with the .
operator.
> add_2 = fn num -> num + 2 end
# โฃcalled with a dot
> add_2.(127) == 129
true
> three = 3
> add_3 = fn num -> num + three end
> add_3.(262) == 265
true
# โฃis our function compromised?
> three = 7
# โฃno, `three` is still 3 in the function
> add_3.(262) == 265
true
Like case blocks, they can have multiple matches, matches can have guards, and do not leak scope:
> support_method = fn ("suspenders") -> IO.puts "Hey big spender."
("belt") -> IO.puts "Who is the real hero?"
a when is_list a -> Enum.each(a, support_method)
_ -> IO.puts "What is this wizzardry?"
end
> support_method.(["belt", "suspenders"])
"Who is the real hero?"
"Hey big spender."
> peanut_butter = "smooth"
# โฃimmediately call
> fn () -> peanut_butter = "chunky" end.()
> peanut_butter == "smooth"
true # scope was not leaked
They cannot have multiple arities (which in case statements wouldn't make sense):
> fn (a) -> "One"
(a, b) -> "Two"
end
# That's a compile error
Comprehensions
Loop over any enumerable or bitstring and build another.
By default they build lists.
generators
, filters
and into:
are comma separated. do
follows Do, End rules.
Basic form:
# โfor โGenerator โBlock
# โฃ โโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโ
> for num <- [1, 2, 3, 4], do: num * num
[1, 4, 9, 16]
They can have filters:
# โFilter
# โโโโโโโโโโดโโโโโโโโ
> for num <- [1, 2, 3, 4], rem(num, 2) == 0, do: num * num
[4, 16]
Multiple generators:
# โGenerator โGenerator
# โโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโ
> for num <- [1,2,3], str <- ["a", "b"], do: "#{num}#{str}"
["1a", "1b", "2a", "2b", "3a", "3b"]
Multiple filters:
# โFilter โFilter
# โโโโโโโดโโโโโโโโโโโโดโโโโโ
> for num <- [1,2,3], str <- ["a", "b"], num !== 3, str !== "a", do: "#{num}#{str}"
["1b", "2b"]
You can pattern match in generators:
> for {_, age} <- %{doug: 4, lucy: 6, ralf: 10}, do: age
[4, 6, 10]
You can build a different data structure with into
:
# โInto a map
# โโโโโโดโโโโโ
> for num <- [1, 2, 3, 4], into: %{}, do: {num, num*num}
%{1 => 1, 2 => 4, 3 => 9, 4 => 16}
# โInto a string
# โโโโโโดโโโโ
> for num <- [1, 2, 3, 4], into: "", do: "the square of #{num} is #{num * num}. "
"the square of 1 is 1. the square of 2 is 4. the square of 3 is 9. the square of 4 is 16. "
Enumerating binaries is a breeze though they have a special syntax:
# โBinary generator
# โโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโ
> for <<byte <- <<255, 12, 55, 89>> >>, do: byte
[255, 12, 55, 89]
# โPattern Match on bit size
# โโโโโดโโโโ
> for <<bit::size(1) <- <<42, 12>> >>, do: bit
[0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0]
> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
# โฃShort hand for bit-size
> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
Work well with streams:
# Grabs 5 lines of input and upcases it
stream = IO.stream(:stdio, :line)
for line <- Enum.take(stream, 5), into: stream do
String.upcase(line)
end
Sigils
Sigils create structures out of text passed to them.
They take the general form ~type| content |m
and can be delimited by {}
, []
, ()
, //
, ||
, ""
, or ''
.
Built in sigils:
Creates | With Interpolation | Without Interpolation |
---|---|---|
String | s | S |
Charlist | c | C |
List of words | w | W |
Regular Expression | r | R |
> a = "Yay!"
> ~s|Quotes #{a} 'used' "willy nilly.| # "Quotes Yay! 'used' \"willy nilly."
> ~S{Not "interpolated" #{a}} # "Not \"interpolated\" \#{a}"
> ~c[charlist "with" 'quotes' #{a}] # 'charlist "with" \'quotes\' Yay!'
> ~w/a list of words #{a}/ # ["a", "list", "of", "words", "Yay!"]
> ~W"a list of words #{a}" # ["a", "list", "of", "words", "\#{a}"]
The ~w
and ~W
sigils can take modifier to specify which type of value to create, a
, c
, or s
for atom
charlist
or string
.
> ~w|alpha bravo charlie|a # [:alpha, :bravo, :charlie]
> ~w|alpha bravo charlie|c # ['alpha', 'bravo', 'charlie']
> ~w|alpha bravo charlie|s # ["alpha", "bravo", "charlie"]
You can also define custom Sigils:
defmodule MySigil do
def sigil_i(string, 'd') do
for num <- String.split(string), do: String.to_integer(num) * 2
end
def sigil_i(string, _opts) do
for num <- String.split(string), do: String.to_integer(num)
end
end
> import MySigil
> ~i|1 2 3 4 5| # [1, 2, 3, 4, 5]
> ~i|1 2 3 4 5|d # [2, 4, 6, 8, 10]
Metaprogramming
- macros
- quote
- unquote
- var!
Processes
Structs
- %ModuleName{}
- implement Map behaviour
- Pattern matching on structs
- @derive
Working with Files
Erlang Interoperability
Erlang modules can be called by prepnding them with a colon.
:crypto.hash(:sha, "Elixir is the beez knees")
|> :crypto.bytes_to_integer
|> Integer.to_string(16) # "62A3326DEDE3EE38C9C85ED6EC87FD888A130D24"
IEx
Interactive Elixir. An Elixir REPL that comes with your Elixir install
Running
run iex
from a command prompt to enter iex.
iex some_file.ex
will compile ./some_file.ex and load it into iex.
Using
iex has built in tab completion. Press tab at an empty prompt to see all functions available.
Some useful functions
- h/0 : Documentation for IEx helpers
- h/1 : Documentation for a Module or Function
- i/1 : Inforamtion about a value
- c/1 : Compile and load a file
- r/1 : Reload a module
TODO:
-S mix
Mix
Task runner, build tool, testing harness.
Run mix help
from the command line for more details.
Applications
mix new PATH
generates the boilerplate for an elixir application.
Run mix help new
for more details.
Tasks
Mix tasks are modules under the Mix.Task
namespace that use Mix.Task
and implement a run/1
function.
If you want your task to show up when mix help
is run you must include a @shortdoc
.
@moduledoc
gets displayed when mix help sometask
is run.
A base mix task file looks like:
defmodule Mix.Tasks.MyTask do
use Mix.Task
@shortdoc "Boilerplate for a task"
@moduledoc """
This task just echos out the options passed to it.
"""
def run(opts) do
IO.inspect(opts)
end
end
Elixir has a built in option parser that you should use for such a task:
http://elixir-lang.org/docs/stable/elixir/OptionParser.html
Tests
Testing is built into the elixir platform via ExUnit.
You can define tests in two ways.
If you are in a Mix Application, you put tests in files under the test
directory.
These tests will be run with mix test
.
You can also place tests at the end of any elixir file and they will be run on compilation.
Test boilerplate looks like:
# โฃ not needed if in a mix project, setup for you in test_helper.exs
ExUnit.start
defmodule MyModuleTest do
use ExUnit.Case
test "the truth" do
assert 1 + 1 == 2
end
end
Debugging in the context of a test
-
First add to the top of your test file (before
defmodule
):require IEx
-
Inside your test, where you want to break into IEx, add:
IEx.pry
-
Run your tests with this invocation:
iex -S mix test --trace <test specification>
The
--trace
prevents iex from timing out after you've broken into pry.
Style Guide
- Module, record, protocol and behaviour names start with an initial cap and are BumpyCase.
- Ex: MyModule
- All other identifiers start with a lowercase letter or an underscore and are snake_case.
- Ex: in_the_air_tonight
- For indentation use 2 spaces. No Tabs.
**Notes** - Regex images generated here: http://jex.im/regulex/