Elixir 1.3

Chapter 4 

Elixir Basics

  • Value types:
    • Arbitrary-sized integers
    • Floating-point numbers
    • Atoms
    • Ranges
    • Regular expressions
  • System types:
    • PIDs and ports
    • References
  • Collection types:
    • Tuples
    • Lists
    • Maps
    • Binaries

Elixir’s built-in types

Integers

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

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.

Atoms

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

Ranges are represented as start..end, where start and end are integers.

Regular Expressions

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”

System Types

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.

Collection Types

Elixir collections can hold values of any type
(including other collections).

  • Tuples
  • Lists
  • Keyword Lists
  • Maps
  • Binaries

Tuples

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"

Tuples

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}

Lists

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.

Lists

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

Keyword Lists

​[ ​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"​

Keyword Lists

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.

Maps

​%{ 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:

Keyword Lists vs Maps

  • Maps allow only one entry for a particular key
  • Keyword lists allow the key to be repeated

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.

Accessing a Map

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

Accessing a Map

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.

Binary Literals

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 >>.

Dates and Times

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.

 

Dates and Times

​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}}"

Names, Source Files,
Conventions, Operators

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 #.

Truth

true, false, and nil

nil is treated as false

Operators

# 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

Boolean Operators

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​

Arithmetic Operators

+ - * / 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/in Operators

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

Variable Scope

  • Elixir is lexically scoped. 
  • The basic unit of scoping is the function body. 
  • Variables defined in a function (including its parameters) are local to that function. 
  • Modules define a scope for local variables, but these are only accessible at the top level of that module, and not in functions defined in the module.
  • Several Elixir structures also define their own scope, like with.

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.

with and Pattern Matching

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

with and Pattern Matching

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 Gotch

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

Thank you!

Made with Slides.com