decimal (1234) hexadecimal (0xcafe) octal (0o765) binary (0b1010)
Decimal may contain underscores—1_000_000.
There is no fixed limit on the size of integers—their internal representation grows to fit their magnitude.
Floating-point numbers are written using a
decimal point.
There must be at least
one digit before and after the decimal point.
An optional trailing exponent may be given.
These are all valid floating-point literals:
1.0 0.2456 0.314159e1 314159.0e-5
Floats are IEEE 754 double precision,
about 16 digits of accuracy and a
maximum exponent of around 10308.
Start with a leading colon (:), which can be followed by an atom word or an Elixir operator.
An atom word is a sequence of letters, digits, underscores, and at signs (@).
May end with an exclamation point or a question mark.
Atoms can contain arbitrary characters by enclosing the characters following the colon in double quotes.
:fred :is_binary? :var@2 :<> :=== :"func/3" :"long john silver"
Two atoms with the same name will always be equal.
Ranges are represented as start..end, where start and end are integers.
Elixir has regular-expression literals, written as ~r{regexp} or ~r{regexp}opts. You can choose any nonalphanumeric characters as delimiters even ~r/…/ for nostalgic reasons but ~r{} is recommended.
You manipulate regular expressions
with the Regex module
iex> Regex.run ~r{[aeiou]}, "caterpillar"
["a"]
iex> Regex.scan ~r{[aeiou]}, "caterpillar"
[["a"], ["e"], ["i"], ["a"]]
iex> Regex.split ~r{[aeiou]}, "caterpillar"
["c", "t", "rp", "ll", "r"]
iex> Regex.replace ~r{[aeiou]}, "caterpillar", "*"
"c*t*rp*ll*r”
These types reflect resources in the
underlying Erlang VM.
PIDs and Ports
A PID is a reference to a local or remote process, and a port is a reference to a resource
(typically external to the application).
The PID of the current process is available by calling self. A new PID is created when you spawn a new process.
References
The function make_ref creates a globally unique reference; no other reference will be equal to it.
We don’t use references in this book.
Elixir collections can hold values of any type
(including other collections).
A tuple is an ordered collection of values.
You write a tuple between braces,
separating the elements with commas.
You can use tuples in pattern matching:
iex> {status, count, action} = {:ok, 42, "next"}
{:ok, 42, "next"}
iex> status
:ok
iex> count
42
iex> action
"next"
A common idiom is to write matches
that assume success:
iex> { :ok, file } = File.open("Rakefile")
{:ok, #PID<0.39.0>}
iex> { :ok, file } = File.open("non-existent-file")
** (MatchError) no match of right hand side value: {:error, :enoent}
A list is effectively a linked data structure. A list may either be empty or consist of a head and a tail. The head contains a value and the tail is itself a list.
iex> [1, 2, 3]
[1, 2, 3]
Because of their implementation, lists are easy to traverse linearly, but they are expensive to access in random order (to get to the nth element, you have to scan through n–1 previous elements).
It is always cheap to get the head of a list and to extract the tail of a list.
iex> [ 1, 2, 3 ] ++ [ 4, 5, 6 ] # concatenation
[1, 2, 3, 4, 5, 6]
iex> [1, 2, 3, 4] -- [2, 4] # difference
[1, 3]
iex> 1 in [1,2,3,4] # membership
true
iex> "wombat" in [1, 2, 3, 4]
false
[ name: "Dave", city: "Dallas", likes: "Programming" ]
Because we often need simple lists of key/value pairs, Elixir gives us a shortcut.
Elixir converts it into a list of two-value tuples:
[ {:name, "Dave"}, {:city, "Dallas"}, {:likes, "Programming"} ]
Elixir allows us to leave off the square brackets if a keyword list is the last argument in a function call.
DB.save record, [ {:use_transaction, true}, {:logging, "HIGH"} ]
# can be:
DB.save record, use_transaction: true, logging: "HIGH"
iex> [1, fred: 1, dave: 2]
[1, {:fred, 1}, {:dave, 2}]
iex> {1, fred: 1, dave: 2}
{1, [fred: 1, dave: 2]}
We can also leave off the brackets if a keyword list appears as the last item in any context where a list of values is expected.
%{ key => value, key => value }
# String keys
iex> states = %{ "AL" => "Alabama", "WI" => "Wisconsin" }
%{"AL" => "Alabama", "WI" => "Wisconsin"}
# Tuple keys
iex> responses = %{ { :error, :enoent } => :fatal, { :error, :busy } => :retry }
%{{:error, :busy} => :retry, {:error, :enoent} => :fatal}
# Atom keys
iex> colors = %{ :red => 0xff0000, :green => 0x00ff00, :blue => 0x0000ff }
%{blue: 255, green: 65280, red: 16711680}
# Mixed keys
iex> %{ "one" => 1, :two => 2, {1,1,1} => 3 }
%{:two => 2, {1, 1, 1} => 3, "one" => 1}
# Expression as a key
iex> name = "José Valim"
"José Valim"
iex> %{ String.downcase(name) => name }
%{"josé valim" => "José Valim"}
A map is a collection of key/value pairs.
A map literal looks like this:
Maps are efficient (particularly as they grow),
and they can be used in Elixir’s pattern matching.
In general,
use keyword lists for things such as command-line parameters and for passing around options,
and use maps when you want an associative array.
You extract values from a map using the key. The square-bracket syntax works with all maps:
%{"AL" => "Alabama", "WI" => "Wisconsin"}
iex> states["AL"]
"Alabama"
iex> states["TX"]
nil
iex> response_types = %{ { :error, :enoent } => :fatal,
...> { :error, :busy } => :retry }
%{{:error, :busy} => :retry, {:error, :enoent} => :fatal}
iex> response_types[{:error,:busy}]
:retry
If the keys are atoms, you can also use a dot notation:
iex> colors = %{ red: 0xff0000, green: 0x00ff00, blue: 0x0000ff }
%{blue: 255, green: 65280, red: 16711680}
iex> colors[:red]
16711680
iex> colors.green
65280
You’ll get a KeyError if there’s no matching key when you use the dot notation.
iex> bin = << 1, 2 >>
<<1, 2>>
iex> byte_size bin
2
# you can specify a size in bits.
iex> bin = <<3 :: size(2), 5 :: size(4), 1 :: size(2)>>
<<213>>
iex> :io.format("~-8.2b~n", :binary.bin_to_list(bin))
11010101
:ok
iex> byte_size bin
1
Binary literals are enclosed
between << and >>.
Elixir 1.3 added a calendar module and four new date and time related types. However, they are currently little more than data holders.
The Calendar module represents the rules used to manipulate dates.
The Date type holds a year, month, day, and a reference to the ruling calendar.
The Time type contains an hour, minute, second, and fractions of a second.
NaiveDateTime contains just a date and a time.
DateTime adds the ability to associate a time zone. The ~N[...] sigil constructs NaiveDateTime structs.
iex> d1 = Date.new(2016, 12, 25)
{:ok, ~D[2016-12-25]}
iex> {:ok, d1} = Date.new(2016, 12, 25)
{:ok, ~D[2016-12-25]}
iex> d2 = ~D[2016-12-25]
~D[2016-12-25]
iex> d1 == d2
true
iex> d1
~D[2016-12-25]
iex> inspect d1, structs: false
"%{__struct__: Date, calendar: Calendar.ISO, day: 25,
month: 12, year: 2016}"
iex(35)> t1 = Time.new(12, 34, 56)
{:ok, ~T[12:34:56]}
iex(36)> t2 = ~T[12:34:56.78]
~T[12:34:56.78]
iex(37)> t1 == t2
false
iex(38)> inspect t2, structs: false
"{:ok, %{__struct__: Time, hour: 12, microsecond: {780000, 2},
minute: 34, second: 56}}"
Elixir identifiers consist of upper and lowercase ASCII characters, digits, and underscores. They may end with a question or an exclamation mark.
Module, record, protocol, and behaviour names start with an uppercase letter and are bumpycase
(like this: BumpyCase). All other identifiers start with a lowercase letter or an underscore, and by convention use underscores between words.
If the first character is an underscore, Elixir doesn’t report a warning if the variable is unused in a pattern match or function parameter list.
Comments start with #.
true, false, and nil
nil is treated as false
# Comparison operators
a === b # strict equality (so 1 === 1.0 is false)
a !== b # strict inequality (so 1 !== 1.0 is true)
a == b # value equality (so 1 == 1.0 is true)
a != b # value inequality (so 1 != 1.0 is false)
a > b # normal comparison
a >= b # :
a < b # :
a <= b # :
The ordering comparisons in Elixir are less strict than in many languages, as you can compare values of different types. If the types are the same or are compatible the comparison uses natural ordering.
number < atom < reference < function < port < pid < tuple < map < list < binary
a or b # true if a is true, otherwise b
a and b # false if a is false, otherwise b
not a # false if a is true, true otherwise
# relaxed boolean operators
a || b # a if a is truthy, otherwise b
a && b # b if a is truthy, otherwise a
!a # false if a is truthy, otherwise true
+ - * / div rem
Integer division yields a floating-point result.
Use div(a,b) to get an integer.
rem is the remainder operator.
It is called as a function (rem(11, 3) => 2).
It differs from normal modulo operations in that the result will have the same sign as the function’s first argument.
Join operators
binary1 <> binary2 # concatenates two binaries (later we'll
# see that binaries include strings)
list1 ++ list2 # concatenates two lists
list1 -- list2 # returns elements in list1 not in list2
a in enum # tests if a is included in enum (for example,
# a list or a range)
The in operator
Several Elixir structures also define their own scope, like with.
with allows you to define a local scope for variables
content = "Now is the time"
lp = with {:ok, file} = File.open("/etc/passwd"),
content = IO.read(file, :all),
:ok = File.close(file),
[_, uid, gid] = Regex.run(~r/_lp:.*?:(\d+):(\d+)/, content)
do
"Group: #{gid}, User: #{uid}"
end
IO.puts lp #=> Group: 26, User: 26
IO.puts content #=> Now is the time
The with expression lets us work with what are effectively temporary variables as we open the file, read its content, close it, and search for the line we want. The value of the with is the value of its do parameter.
The inner variable content is local to the with, and does not affect the variable in the outer scope.
You can user <- instead of = in a with
and if it fails to perform a match it will
return the value that couldn't be matched.
iex> with [a|_] <- [1,2,3], do: a
1
iex> with [a|_] <- nil, do: a
nil
We can use this to let the with in the previous example return nil if the user can’t be found,
rather than raising an exception.
result = with {:ok, file} = File.open("/etc/passwd"),
content = IO.read(file, :all),
:ok = File.close(file),
[_, uid, gid] <- Regex.run(~r/xxx:.*?:(\d+):(\d+)/, content)
do
"Group: #{gid}, User: #{uid}"
end
IO.puts inspect(result) #=> nil
with is treated as a macro by Elixir so you must put a param of the first line or use parentheses.
mean = with count = Enum.count(values),
sum = Enum.sum(values)
do
sum/count
end
# use parentheses:
mean = with(
count = Enum.count(values),
sum = Enum.sum(values)
do
sum/count
end)
# shortcut
mean = with count = Enum.count(values),
sum = Enum.sum(values),
do: sum/count