Chapter 2. A whirlwind tour

published book

This chapter covers

  • Your first Elixir program
  • Using Interactive Elixir (iex)
  • Data types
  • Pattern matching
  • List and recursion
  • Modules and functions
  • The pipe (|>) operator
  • Erlang interoperability

Instead of discussing each Elixir language feature in depth, I’m going to present them as a series of examples. I’ll elaborate more when we come to concepts that may seem unfamiliar to, say, a Java or Ruby programmer. For certain concepts, you can probably draw parallels from whatever languages you already know. The examples will be progressively more fun and will highlight almost everything you need to understand the Elixir code in this book.

join today to enjoy all our content. all the time.
 

2.1. Setting up your environment

Elixir is supported by most of the major editors, such as Vim, Emacs, Spacemacs, Atom, IntelliJ, and Visual Studio, to name a few. The aptly named Alchemist (https://github.com/tonini/alchemist.el), the Elixir tooling integration that works with Emacs/Spacemacs, provides an excellent developer experience. It features things like documentation lookup, smart code completion, integration with iex and mix, and a ton of other useful features. It’s by far the most supported and feature-rich of the editor integrations. Get your terminal and editor ready, because the whirlwind tour begins now.

Get The Little Elixir & OTP Guidebook
add to cart

2.2. First steps

Let’s begin with something simple. Due to choices made by my former colonial masters (I’m from Singapore), I’m woefully unfamiliar with measurements in feet, inches, and so on. We’re going to write a length converter to remedy that.

Here’s how you can define the length converter in Elixir. Enter the code in the following listing into your favorite text editor and save the file as length_converter.ex.

Listing 2.1. Length converter program in Elixir (length_converter.ex)
defmodule MeterToFootConverter do
  def convert(m) do
    m * 3.28084
  end
end

defmodule defines a new module (MeterToFootConverter), and def defines a new function (convert).

2.2.1. Running an Elixir program in Interactive Elixir

Interactive Elixir (iex for short) is the equivalent of irb in Ruby or node in Node.js. In your terminal, launch iex with the filename as the argument:

% iex length_converter.ex

Interactive Elixir (0.13.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":2},{\"line\":0,\"ch\":25}]]"}
!@%STYLE%@!

The record for the tallest man in the world is 2.72 m. What’s that in feet? Let’s find out:

iex> MeterToFeetConverter.convert(2.72)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":39}]]"}
!@%STYLE%@!

The result is

8.9238848

2.2.2. Stopping an Elixir program

There are a few ways to stop an Elixir program or exit iex. The first way is to press Ctrl-C. The first time you do this, you’ll see the following:

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution

You can now either press A to abort or press Ctrl-C again. An alternative is to use System.halt, although personally I’m more of a Ctrl-C person.

2.2.3. Getting help

Because iex is your primary tool for interacting with Elixir, it pays to learn a bit more about it. In particular, iex features a sweet built-in documentation system. Fire up iex again. Let’s say you want to learn about the Dict module. To do so, type h Dict in iex. The output will be similar to that shown in figure 2.1.

Figure 2.1. Documentation for the Dict module displayed in iex

Want to know the functions available in Dict? Type Dict. (the dot is important!), and then press the Tab key. You’ll see a list of functions available in the Dict module, as shown in figure 2.2.

Figure 2.2. A list of functions available in the Dict module

Now, let’s say you want to learn more about the put/3 function. (I’ll explain the /3 in detail later. For now, it means this version of put accepts three arguments.) In iex, type h Dict.put/3. The output will look like figure 2.3.

Figure 2.3. Documentation for Dict.put/3

Pretty neat, eh? What’s even better is that the documentation is beautifully syntax-highlighted.

Sign in for more free preview time

2.3. Data types

Here are the common data types we’ll use in this book:

  • Modules
  • Functions
  • Numbers
  • Strings
  • Atoms
  • Tuples
  • Maps

This section introduces each of them in more depth.

2.3.1. Modules, functions, and function clauses

Modules are Elixir’s way of grouping functions together. Examples of modules are List, String, and, of course, MeterToFootConverter. You create a module using defmodule. Similarly, you create functions using def.

Modules

Just for kicks, let’s write a function to convert meters into inches. You need to make a few changes in the current implementation. First, the module name is too specific. Let’s change that to something more general:

defmodule MeterToLengthConverter do
  # ...
end

More interestingly, how do you add a function that converts from meters to inches? The next listing shows one possible approach.

Listing 2.2. Nesting defmodules to convert meters to inches
defmodule MeterToLengthConverter do
  defmodule Feet do
    def convert(m) do
      m * 3.28084
    end

  end
  defmodule Inch do
    def convert(m) do
      m * 39.3701
    end
  end
end

Now you can compute the height of the world’s tallest man in inches:

iex> MeterToLengthConverter.Inch.convert(2.72)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":46}]]"}
!@%STYLE%@!

Here’s the result:

107.08667200000001

This example illustrates that modules can be nested. The modules Feet and Inch are nested within MeterToLengthConverter. To access a function in a nested module, you use dot notation. In general, to invoke functions in Elixir, the following format is used:

Module.function(arg1, arg2, ...)
Note

In mailing lists, this format is sometimes known as MFA (Module, Function, and Arguments). Remember this format because you’ll encounter it again in the book.

You can also flatten the module hierarchy, as shown in the next listing.

Listing 2.3. Flattening the module hierarchy (Interactive Elixir)
defmodule MeterToLengthConverter.Feet do            #1
  def convert(m) do
    m * 3.28084
  end  
end       
defmodule MeterToLengthConverter.Inch do            #1
  def convert(m) do
    m * 39.3701
  end  
end

You can call the function exactly the same way you did previously.

Functions and function clauses

There’s a more idiomatic way to write the length converter: by using function clauses. Here’s a revised version:

defmodule MeterToLengthConverter do
  def convert(:feet, m) do
    m * 3.28084
  end

  def convert(:inch, m) do
    m * 39.3701
  end
end

Defining a function is straightforward. Most functions are written like this:

def convert(:feet, m) do
  m * 3.28084
end

Single-lined functions are written like so:

def convert(:feet, m), do: m * 3.28084

While we’re at it, let’s add another function to convert meters to yards, this time using the single-line variety:

defmodule MeterToLengthConverter do
  def convert(:feet, m), do: m * 3.28084
  def convert(:inch, m), do: m * 39.3701
  def convert(:yard, m), do: m * 1.09361
end

Functions are referred to by their arity: the number of arguments they take. Therefore, we refer to the previous function as convert/2. This is an example of a named function. Elixir also has the notion of anonymous functions. Here’s a common example of an anonymous function:

iex> Enum.map([1, 2, 3], fn x -> x*x end)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":41}]]"}
!@%STYLE%@!

The result is as follows:

[1, 4, 9]

You can define a function with the same name multiple times, as in the example. The important thing to notice is that they must be grouped together. Therefore, this is bad form:

defmodule MeterToLengthConverter do
  def convert(:feet, m), do: m * 3.28084
  def convert(:inch, m), do: m * 39.3701
  def i_should_not_be_here, do: IO.puts "Oops"              #1
  def convert(:yard, m), do: m * 1.09361
end

Elixir will complain accordingly:

% iex length_converter.ex
length_converter.ex:5: warning: clauses for the same def
should be grouped together,
def convert/2 was previously defined


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":2},{\"line\":0,\"ch\":25}]]"}
!@%STYLE%@!

Another important thing: order matters. Each function clause is matched in a top-down fashion. This means once Elixir finds a compatible function clause that matches (arity and/or arguments), it will stop searching and execute that function. For the current length converter, moving function clauses around won’t affect anything. When we explore recursion later, you’ll begin to appreciate why ordering of function clauses matters.

2.3.2. Numbers

Numbers in Elixir work much as you’d expect from traditional programming languages. Here’s an example that operates on an integer, a hexadecimal, and a float:

iex> 1 + 0x2F / 3.0
16.666666666666664


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":19}]]"}
!@%STYLE%@!

And here are the division and remainder functions:

iex> div(10,3)
3
iex> rem(10,3)
1


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":14}],[{\"line\":2,\"ch\":5},{\"line\":2,\"ch\":14}]]"}
!@%STYLE%@!

2.3.3. Strings

Strings in Elixir lead two lives, as this section explains. On the surface, strings look pretty standard. Here’s an example that demonstrates string interpolation:

iex(1)> "Strings are #{:great}!"


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":32}]]"}
!@%STYLE%@!

It gives you

"Strings are great!"

You can also perform various operations on strings:

iex(2)> "Strings are #{:great}!" |> String.upcase |> String.reverse


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":67}]]"}
!@%STYLE%@!

This returns

"!TAERG ERA SGNIRTS"
Strings are binaries

How do you test for a string? There isn’t an is_string/1 function available. That’s because a string in Elixir is a binary. A binary is a sequence of bytes:

iex(3)> "Strings are binaries" |> is_binary


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":43}]]"}
!@%STYLE%@!

This returns

true

One way to show the binary representation of a string is to use the binary concatenation operator <> to attach a null byte, <<0>>:

iex(4)> "ohai" <> <<0>>


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":23}]]"}
!@%STYLE%@!

This returns

<<111, 104, 97, 105, 0>>.

Each individual number represents a character:

iex(5)> ?o
111

iex(6)> ?h
104

iex(7)> ?a
97

iex(8)> ?i
105


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":10}],[{\"line\":3,\"ch\":8},{\"line\":3,\"ch\":10}],[{\"line\":6,\"ch\":8},{\"line\":6,\"ch\":10}],[{\"line\":9,\"ch\":8},{\"line\":9,\"ch\":10}]]"}
!@%STYLE%@!

To further convince yourself that the binary representation is equivalent, try this:

iex(44)> IO.puts <<111, 104, 97, 105>>


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":9},{\"line\":0,\"ch\":38}]]"}
!@%STYLE%@!

This gives you back the original string:

ohai
Strings aren’t char lists

A char list, as its name suggests, is a list of characters. It’s an entirely different data type than strings, and this can be confusing. Whereas strings are always enclosed in double quotes, char lists are enclosed in single quotes. For example, this

iex(9)> 'ohai' == "ohai"


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":24}]]"}
!@%STYLE%@!

results in false. You usually won’t use char lists in Elixir. But when talking to some Erlang libraries, you’ll have to. For example, as you’ll see in a later example, the Erlang HTTP client (httpc) accepts a char list as the URL:

:httpc.request 'http://www.elixir-lang.org'

What happens if you pass in a string (binary) instead? Try it:

iex(51)> :httpc.request "http://www.elixir-lang.org"
** (ArgumentError) argument error
            :erlang.tl("http://www.elixir-lang.org")
    (inets) inets_regexp.erl:80: :inets_regexp.first_match/3
    (inets) inets_regexp.erl:68: :inets_regexp.first_match/2
    (inets) http_uri.erl:186: :http_uri.split_uri/5
    (inets) http_uri.erl:136: :http_uri.parse_scheme/2
    (inets) http_uri.erl:88: :http_uri.parse/2
    (inets) httpc.erl:162: :httpc.request/5


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":9},{\"line\":0,\"ch\":52}]]"}
!@%STYLE%@!

We’ll cover calling Erlang libraries later in the chapter, but this is something you need to keep in mind when you’re dealing with certain Erlang libraries.

2.3.4. Atoms

Atoms serve as constants, akin to Ruby’s symbols. Atoms always start with a colon. There are two different ways to create atoms. For example, both :hello_atom and :"Hello Atom" are valid atoms. Atoms are not the same as strings—they’re completely separate data types:

iex> :hello_atom == "hello_atom"
false


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":32}]]"}
!@%STYLE%@!

On their own, atoms aren’t very interesting. But when you place atoms into tuples and use them in the context of pattern matching, you’ll begin to understand their role and how Elixir exploits them to write declarative code. We’ll get to pattern matching in section 2.5. For now, let’s turn our attention to tuples.

2.3.5. Tuples

A tuple can contain different types of data. For example, an HTTP client might return a successful request in the form of a tuple like this:

{200, "http://www.elixir-lang.org"}

Here’s how the result of an unsuccessful request might look:

{404, "http://www.php-is-awesome.org"}

Tuples use zero-based access, just as you access array elements in most programming languages. Therefore, if you want the URL of the request result, you need to pass in 1 to elem/2

iex> elem({404, "http://www.php-is-awesome.org"}, 1)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":52}]]"}
!@%STYLE%@!

which will return http://www.php-is-awesome.org.

You can update a tuple using put_elem/3

iex> put_elem({404, "http://www.php-is-awesome.org"}, 0, 503)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":61}]]"}
!@%STYLE%@!

which returns

{503, "http://www.php-is-awesome.org"}

2.3.6. Maps

A map is essentially a key-value pair, like a hash or dictionary, depending on the language. All map operations are exposed with the Map module. Working with maps is straightforward, with a tiny caveat. (See the following sidebar on immutability.) See if you can spot it in the examples. Let’s start with an empty map:

iex> programmers = Map.new
%{}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":26}]]"}
!@%STYLE%@!

Now, let’s add some smart people to the map:

iex> programmers = Map.put(programmers, :joe, "Erlang")
%{joe: "Erlang"}

iex> programmers = Map.put(programmers, :matz, "Ruby")
%{joe: "Erlang", matz: "Ruby"}

iex> programmers = Map.put(programmers, :rich, "Clojure")
%{joe: "Erlang", matz: "Ruby", rich: "Clojure"}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":55}],[{\"line\":3,\"ch\":5},{\"line\":3,\"ch\":54}],[{\"line\":6,\"ch\":5},{\"line\":6,\"ch\":57}]]"}
!@%STYLE%@!
A very important aside: immutability

Notice that programmers is one of the arguments to Map.put/3, and it’s re-bound to programmers. Why is that? Here’s another example:

iex> Map.put(programmers, :rasmus, "PHP")
%{joe: "Erlang", matz: "Ruby", rasmus: "PHP", rich: "Clojure"}
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":41}]]"}
!@%STYLE%@!

The return value contains the new entry. Let’s check the contents of programmers:

iex> programmers
%{joe: "Erlang", matz: "Ruby", rich: "Clojure"}
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":16}]]"}
!@%STYLE%@!

This property is called immutability.

All data structures in Elixir are immutable, which means you can’t make any modifications to them. Any modifications you make always leave the original unchanged. A modified copy is returned. Therefore, in order to capture the result, you can either rebind it to the same variable name or bind the value to another variable.

join today to enjoy all our content. all the time.
 

2.4. Guards

Let’s look at length_converter.ex once more. Suppose you want to ensure that the arguments are always numbers. You can modify the program by adding guard clauses:

defmodule MeterToLengthConverter do
  def convert(:feet, m) when is_number(m), do: m * 3.28084           #1
  def convert(:inch, m) when is_number(m), do: m * 39.3701           #1
  def convert(:yard, m) when is_number(m), do: m * 1.09361           #1
end

Now, if you try something like MeterToLengthConverter.convert(:feet, "smelly"), none of the function clauses will match. Elixir will throw a FunctionClauseError:

iex(1)> MeterToLengthConverter.convert(:feet, "smelly")
(FunctionClauseError) no function clause matching in convert/2


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":55}]]"}
!@%STYLE%@!

Negative lengths make no sense. Let’s make sure the arguments are non-negative. You can do this by adding another guard expression:

defmodule MeterToLengthConverter do
  def convert(:feet, m) when is_number(m) and m >= 0, do: m * 3.28084      #1
  def convert(:inch, m) when is_number(m) and m >= 0, do: m * 39.3701      #1
  def convert(:yard, m) when is_number(m) and m >= 0, do: m * 1.09361      #1
end

In addition to is_number/1, other similar functions will come in handy when you need to differentiate between the various data types. To generate this list, fire up iex, and type is_ followed by pressing the Tab key:

iex(1)> is_
is_atom/1         is_binary/1       is_bitstring/1    is_boolean/1
is_float/1        is_function/1     is_function/2     is_integer/1
is_list/1         is_map/1          is_nil/1          is_number/1
is_pid/1          is_port/1         is_reference/1    is_tuple/1


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":11}],[{\"line\":1,\"ch\":0},{\"line\":1,\"ch\":3}],[{\"line\":2,\"ch\":0},{\"line\":2,\"ch\":3}],[{\"line\":3,\"ch\":0},{\"line\":3,\"ch\":3}],[{\"line\":4,\"ch\":0},{\"line\":4,\"ch\":3}]]"}
!@%STYLE%@!

The is_* functions should be self-explanatory, except for is_port/1 and is_reference/1. You won’t use ports in this book, and you’ll meet references in chapter 6 and see how they’re useful in giving messages a unique identity. Guard clauses are especially useful for eliminating conditionals and, as you may have guessed, for making sure arguments are of the correct type.

Sign in for more free preview time

2.5. Pattern matching

Pattern matching is one of the most powerful features in functional programming languages, and Elixir is no exception. In fact, pattern matching is one of my favorite features in Elixir. Once you see what pattern matching can do, you’ll start to yearn for it in languages that don’t support it.

Elixir uses the equals operator (=) to perform pattern matching. Unlike most languages, Elixir uses the = operator for more than variable assignment; = is called the match operator. From now on, when you see =, think matches instead of equals. What are you matching, exactly? In short, pattern matching is used to match both values and data structures. In this section, you’ll learn to love pattern matching as a powerful tool you can use to produce beautiful code. First, let’s learn the rules.

2.5.1. Using = for assigning

The first rule of the match operator is that variable assignments only happen when the variable is on the left side of the expression. For example:

iex> programmers = Map.put(programmers, :jose, "Elixir")


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":56}]]"}
!@%STYLE%@!

This is the result:

%{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}

Here, you assign the result of Map.put/2 to programmers. As expected, programmers contains the following:

iex> programmers
%{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":16}]]"}
!@%STYLE%@!

2.5.2. Using = for matching

Here’s when things get slightly interesting. Let’s swap the order of the previous expression:

iex> %{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}
 = programmers
%{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":68}],[{\"line\":2,\"ch\":0},{\"line\":2,\"ch\":63}],[{\"line\":1,\"ch\":1},{\"line\":1,\"ch\":14}]]"}
!@%STYLE%@!

Notice that this is not an assignment. Instead, a successful pattern match has occurred, because the contents of both the left side and programmers are identical.

Next, let’s see an unsuccessful pattern match:

iex> %{tolkien: "Elvish"} = programmers
** (MatchError) no match of right hand side value: %{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":39}]]"}
!@%STYLE%@!

When an unsuccessful match occurs, a MatchError is raised. Let’s look at destructuring next because you’ll need this to perform some cool tricks with pattern matching.

2.5.3. Destructuring

Destructuring is where pattern matching shines. One of the nicest definitions of destructuring comes from Common Lisp: The Language:[1] “Destructuring allows you to bind a set of variables to a corresponding set of values anywhere that you can normally bind a value to a single variable.” Here’s what that means in code:

1 “Destructuring,” in Common Lisp: The Language, 2nd ed., by Guy L. Steele Jr. (Digital Press, 1990).

iex> %{joe: a, jose: b, matz: c, rich: d} =
     %{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}
%{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":43}]]"}
!@%STYLE%@!

Here are the contents of each of the variables:

iex> a
"Erlang"

iex> b
"Elixir"

iex> c
"Ruby"

iex> d
"Clojure"


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":6}],[{\"line\":1,\"ch\":4},{\"line\":1,\"ch\":5}],[{\"line\":3,\"ch\":5},{\"line\":3,\"ch\":6}],[{\"line\":7,\"ch\":3},{\"line\":7,\"ch\":4}],[{\"line\":6,\"ch\":5},{\"line\":6,\"ch\":6}],[{\"line\":9,\"ch\":5},{\"line\":9,\"ch\":6}]]"}
!@%STYLE%@!

In this example, you bind a set of variables (a, b, c, and d) to a corresponding set of values (“Erlang”, “Elixir”, “Ruby”, and “Clojure”). What if you’re only interested in extracting some of the information? No problem, because you can do pattern matching without needing to specify the entire pattern:

iex> %{jose: most_awesome_language} = programmers
%{joe: "Erlang", jose: "Elixir", matz: "Ruby", rich: "Clojure"}
iex> most_awesome_language
"Elixir"


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":49}],[{\"line\":0,\"ch\":13},{\"line\":0,\"ch\":34}],[{\"line\":2,\"ch\":5},{\"line\":2,\"ch\":26}]]"}
!@%STYLE%@!

This will come in handy when you’re only interesting in extracting a few pieces of information.

Here’s another useful technique that’s used often in Elixir programs. Notice the return values of these two expressions:

iex> Map.fetch(programmers, :rich)
{:ok, "Clojure"}
iex> Map.fetch(programmers, :rasmus)
:error


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":34}],[{\"line\":2,\"ch\":5},{\"line\":2,\"ch\":36}]]"}
!@%STYLE%@!

A tuple with the atom :ok and the value (the programming language) is returned when a key is found, or an :error atom otherwise. You can see how tuples and atoms are useful and how you can exploit this with pattern matching. By using the return values of both the happy ({:ok, language}) and exceptional paths (:error), you can express yourself as follows:

iex> case Map.fetch(programmers, :rich) do
...>   {:ok, language} ->
...>     IO.puts "#{language} is a legit language."
...>   :error ->
...>     IO.puts "No idea what language this is."
...> end


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":42}],[{\"line\":1,\"ch\":7},{\"line\":1,\"ch\":25}],[{\"line\":2,\"ch\":9},{\"line\":2,\"ch\":51}],[{\"line\":3,\"ch\":7},{\"line\":3,\"ch\":16}],[{\"line\":4,\"ch\":9},{\"line\":4,\"ch\":49}],[{\"line\":5,\"ch\":5},{\"line\":5,\"ch\":8}]]"}
!@%STYLE%@!

This returns

Clojure is a legit language.
Example: reading a file

Destructuring is useful for declaring preconditions in your programs. What do I mean by that? Let’s take reading a file as an example. If most of your logic depends on the file being readable, then it makes sense to find out as soon as possible whether an error occurs with file reading. It would also be helpful to know what kind of error occurred. Figure 2.4 shows a snippet from the File.read/1 documentation.

Figure 2.4. Documentation for File.read/1

What can you learn from reading this documentation?

  • For a successful read, File.read/1 returns a {:ok, binary} tuple. Note that binary is the entire contents of the read file.
  • Otherwise, a {:error, posix} tuple is returned. The variable posix contains the reason for the error, which is an atom such as :enoent or :eaccess.

Here’s an example of the code to read a file:

case File.read("KISS - Beth.mp3") do
  {:ok, binary} ->
    IO.puts "KIϟϟ rocks!"
  {:error, reason} ->
    IO.puts "No Rock N Roll for anyone today because of #{reason}."
end
Example: tic-tac-toe board

Listing 2.4 is an illustrative example of a Tic-Tac-Toe application. The check_board/1 function checks whether the tic-tac-toe’s board configuration is a winning combination. The board is expressed using tuples. Notice how you “draw” the board using tuples and how easy the code is to understand.

Listing 2.4. Tic-tac-toe board that uses tuples to represent board configurations
def check_board(board) do
  case board do
    { :x, :x, :x,
      _ , _ , _ ,
      _ , _ , _ } -> :x_win
    { _ , _ , _ ,
      :x, :x, :x,
      _ , _ , _ } -> :x_win

    { _ , _ , _ ,
      _ , _ , _ ,
      :x, :x, :x} -> :x_win

    { :x, _ , _ ,
      :x, _ , _ ,
      :x, _ , _ } -> :x_win

    { _ , :x, _ ,
      _ , :x, _ ,
      _ , :x, _ } -> :x_win

    { _ , _ , :x,
      _ , _ , :x,
      _ , _ , :x} -> :x_win

    { :x, _ , _ ,
      _ , :x, _ ,
      _ , _ , :x} -> :x_win

    { _ , _ , :x,
      _ , :x, _ ,
      :x, _ , _ } -> :x_win

    # Player O board patterns omitted ...

    { a, b, c,
      d, e, f,
      g, h, i } when a and b and c and d and e and f and g and h and i -> :draw

    _ -> :in_progress

  end
end

Note that the underscore (_) is the “don’t care” or “match everything” operator. You’ll see quite a few examples of it in this book. And you’ll see more pattern-matching in section 2.6 when we look at lists.

Example: parsing an MP3 file

Elixir is brilliant for parsing binary data. In this example, you’ll extract metadata from an MP3 file; it’s also a good exercise to reinforce some of the concepts you’ve learned. Before you parse a binary, you must know the layout. The information you’re interested in, the ID3 tag, is located in the last 128 bytes of the MP3 (see figure 2.5).

Figure 2.5. The ID3 tag is located in the last 128 bytes of the MP3.

You must somehow ignore the audio data portion and concentrate only on the ID3 tag. The diagram in figure 2.6 shows the ID3 tag’s layout. The first three bytes are called the header and contain three characters: “T”, “A”, and “G”. The next 30 bytes contain the title. The next 30 bytes are the artist, followed by another 30 bytes containing the album. The next four bytes are the year (such as “2”, “0”, “1”, “4”).

Figure 2.6. The layout of the ID3 tag

Try to imagine how you might extract this metadata in some other programming language. Listing 2.5 shows the Elixir version; save the file as id3.ex.

Listing 2.5. Full ID3-parsing program (id3.ex)
defmodule ID3Parser do
  def parse(file_name) do
    case File.read(file_name) do                                               #1
      {:ok, mp3} ->                                                            #2
        mp3_byte_size = byte_size(mp3) – 128                                   #3
        << _ :: binary-size(mp3_byte_size), id3_tag :: binary >> = mp3         #4
        << "TAG", title   :: binary-size(30),
                  artist  :: binary-size(30),
                  album   :: binary-size(30),
                  year    :: binary-size(4),
                  _rest   :: binary >>       = id3_tag                         #5
        IO.puts "#{artist} - #{title} (#{album}, #{year})"
      _ ->                                                                     #6
        IO.puts "Couldn't open #{file_name}"
    end
  end
end

Here’s an example run of the program:

% iex id3.ex

iex(1)> ID3Parser.parse "sample.mp3"


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":2},{\"line\":0,\"ch\":12}],[{\"line\":2,\"ch\":8},{\"line\":2,\"ch\":36}]]"}
!@%STYLE%@!

And here’s an example result:

Lana Del Rey - Ultraviolence (Ultraviolence, 2014)
:ok

Let’s walk through the program. First the program reads the MP3. A happy path will return a tuple that matches {:ok, mp3}, where mp3 contains the binary contents of the file. Otherwise, the catch-all _ operator will match a failed file read.

Because you’re only interested in the ID3 tag, you need a way to skip ahead. You first compute the size in bytes of the audio portion of the binary. Once you have this information, you can use the size of the audio portion to tell Elixir how to destructure the binary. You pattern-match the MP3 by declaring a pattern on the left and the mp3 variable on the right. Recall that variable assignment takes place when the variable is on the left side of an expression, and pattern matching is attempted otherwise (see figure 2.7).

Figure 2.7. How the MP3 is destructured

You may recognize the << >>: it’s used to represent an Elixir binary. You then declare that you aren’t interested in the audio part. How? By specifying the binary size you computed previously. What remains is the ID3 tag, which is captured in the id3_tag variable. Now you’re free to extract the information from the ID3 tag!

To do that, you perform another pattern match with the declared pattern on the left and id3_tag on the right. By declaring the appropriate number of bytes, you can capture the title, the artist, and other information in the respective variables (see figure 2.8).

Figure 2.8. Destructuring the ID3 binary
Tour livebook

Take our tour and find out more about liveBook's features:

  • Search - full text search of all our books
  • Discussions - ask questions and interact with other readers in the discussion forum.
  • Highlight, annotate, or bookmark.
take the tour

2.6. Lists

Lists are another data type in Elixir. You can do quite a few interesting things with lists, and they therefore deserve their own section.

Lists are somewhat similar to linked lists[2] in that random access is essentially a O(n) (linear) operation. Here’s the recursive definition of a list: a non-empty list consists of a head and a tail. The tail is also a list. Here it is, translated to code:

iex> [1, 2, 3] == [1 | [2 | [3 | []]]]
true


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":38}]]"}
!@%STYLE%@!

A diagram illustrates this better, as shown in figure 2.9.

Figure 2.9. [1,2,3] represented as a picture

Let’s try to understand this picture by starting at the outermost box. This says the head of the list is 1, followed by the tail of the list. This tail, in turn, is another list: the head of this list is 2, followed by the tail, which (again) is another list. Finally, this list (in the third box) consists of a head of 3 and a tail. This tail is an empty list. The tail of the final element of any list is always an empty list. Recursive functions make use of this fact to determine when the end of a list is reached.

You can also use the pattern-matching operator to prove that [1, 2, 3] and [1 | [2 | [3 | []]]] are the same thing:

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


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":37}]]"}
!@%STYLE%@!

Because no MatchError occurs, you can be certain that both representations of the list are equivalent. Of course, you won’t be typing [1|[2|[3|[]]]] in your day-to-day code; this is just to emphasize that a list is a recursive data structure.

I haven’t explained what the | is. This is commonly called the cons operator.[3] When applied to lists, it separates the head and tail. That is, the list is destructured. This is another instance of pattern matching in action:

3 Short for construct. See http://en.wikipedia.org/wiki/Cons for more information.

iex> [head | tail] = [1, 2, 3]
[1, 2, 3]


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":30}]]"}
!@%STYLE%@!

Let’s check the contents of head and tail:

defmodule MyList do
  def flatten([]), do: []                        #1
  def flatten([ head | tail ]) do                #2
    flatten(head) ++ flatten(tail)               #2
  end
  def flatten(head), do: [ head ]                #3
end

Notice that tail is also a list, which is in line with the definition. You can also use the cons operator to add (or append) to the beginning of a list:

iex(1)> list = [1, 2, 3]
[1, 2, 3]
iex(2)> [0 | list ]
[0, 1, 2, 3]


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":24}],[{\"line\":2,\"ch\":8},{\"line\":2,\"ch\":19}]]"}
!@%STYLE%@!

You can use the ++ operator to concatenate lists:

iex(3)> [0] ++ [1, 2, 3]
[0, 1, 2, 3]


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":24}]]"}
!@%STYLE%@!

What about a list with a single element? If you understood figure 2.9, then this is a piece of cake:

iex(1)> [ head | tail ] = [:lonely]
[:lonely]
iex(2)> head
:lonely
iex(3)> tail
[]


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":35}],[{\"line\":0,\"ch\":10},{\"line\":0,\"ch\":14}],[{\"line\":2,\"ch\":8},{\"line\":2,\"ch\":12}],[{\"line\":0,\"ch\":17},{\"line\":0,\"ch\":21}],[{\"line\":4,\"ch\":8},{\"line\":4,\"ch\":12}]]"}
!@%STYLE%@!

This list contains a single atom. Notice that tail is an empty list; this may seem strange at first, but if you think about it, it fits the definition. It’s precisely this definition that allows you to do interesting things with lists and recursion, which we examine next.

2.6.1. Example: flattening a list

Now that you know how lists work, let’s build a flatten/1 function. flatten/1 takes in a possibly nested list and returns a flattened version. Flattening a list can be useful, especially if the list is used to represent a tree data structure;[4] flattening the tree returns all the elements contained in the tree. Let’s see an example:

List.flatten [1, [:two], ["three", []]]

This returns

[1, :two, "three"]

Here’s one possible implementation of flatten/1:

defmodule MyList do
  def flatten([ head | tail ]) do 
    flatten(head) ++ flatten(tail)
  end
  def flatten(head), do: [ head ]
  def flatten([]), do: []                     #1
end

Take a moment to digest the code, because there’s more to it than meets the eye. There are three cases to consider.

You begin with the base case (or degenerate case, if you’ve taken a computer science course): an empty list . If you get an empty list, you return an empty list.

For a non-empty list , you use the cons operator to split the list into head and tail. You then recursively call flatten/1 on both head and tail. Next, the result is concatenated using the ++ operator. Note that head can also be a nested list; for example, [[1], 2] means head is [1].

If you get a non-list argument, you turn it into a list. Now, consider what happens to a list such as [[1], 2]. It helps to trace the execution on paper:

1.  The first function clause doesn’t match.

2.  The second function clause matches. In this case, you pattern-match the list: head is [1], and tail is 2. Now, flatten([1]) and flatten(2) are called recursively.

3.  Handle flatten([1]). Again it doesn’t match the first clause . The second one matches. head is 1, and tail is [].

4.  flatten(1) is called. The third function clause matches, and it returns [1]. flatten([]) matches the first clause and returns []. A previous call to flatten(2) (see step 2) returns [2]. [1] ++ [] ++ [2] yields the flattened list.

Don’t despair if you don’t get that the first time through. As with most things, practice will go a long way in helping your understanding. Also, you’ll see numerous examples in the upcoming chapters.

2.6.2. Ordering of function clauses

I previously mentioned that the order of function clauses matters. This is a perfect place to explain why. Consider this example:

"/Users/Ben/Books"                                           #1
  |> Path.join("**/*.epub")                                  #2
  |> Path.wildcard                                           #3
  |> Enum.filter(fn fname ->                                 #4
       String.contains?(Path.basename(fname), "Java") 
     end)

The base case is the last clause. What will happen if you try MyList.flatten([])? You’d expect the result to be [], but in fact you’d get back [[]]. If you give it a little thought, you’ll realize that never runs. The reason is that the second function clause will match [], and therefore the third function clause will be ignored.

Let’s try running this for real:

% iex length_converter.ex
warning: this clause cannot match because a previous clause at
line 7 always matches


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":2},{\"line\":0,\"ch\":25}]]"}
!@%STYLE%@!

Elixir has your back! Take heed of warnings like this because they can save you hours of debugging headaches. An unmatched clause can mean dead code or, in the worst case, an infinite loop.

join today to enjoy all our content. all the time.
 

2.7. Meet |>, the pipe operator

I’d like to introduce one of the most useful operators ever invented in programming-language history: the pipe operator, |>.[5] It takes the result of the expression on the left and inserts it as the first parameter of the function call on the right. Let’s look at a code snippet from an Elixir program I wrote recently. Without the pipe operator, this is how I would have written it:

5 Here’s a little trivia: the |> operator is inspired by F#.

defmodule URLWorker do
  def start(url) do
    do_request(HTTPoison.get(url))
  end
  # ...
end

HTTPoison is a HTTP client. It takes url and returns the HTML page. The page is then passed to the do_request function to perform some parsing. Notice that in this version, you have to look for the innermost brackets to locate url and then move outward as you mentally trace the successive function calls.

Now, I present you with the version that uses pipe operators:

defmodule URLWorker do
  def start(url) do
    result = url |> HTTPoison.get |> do_request
  end
  # ...
end

No contest, right? Many of the examples in this book make extensive use of |>. The more you use it, the more you’ll start to see data as being transformed from one form to another, something like an assembly line. When you use it often enough, you’ll begin to miss it when you program in other languages.

2.7.1. Example: filtering files in a directory by filename

Let’s say you have a directory filled with e-books, and this directory could potentially have folders nested within it. You want to get the filenames of only the Java-related EPUBs—that is, you only want books that have filenames that end with *.epub and that include “Java”. Here’s how to do it:

Here’s some example output:

["/Users/Ben/Books/Java/Java_Concurrency_In_Practice.epub",
"/Users/Ben/Books/Javascript/JavaScript Patterns.epub",
"/Users/Ben/Books/Javascript/Functional_JavaScript.epub",
"/Users/Ben/Books/Ruby/Using_JRuby_Bringing_Ruby_to_Java.epub"]

It’s nice to read code in which the steps are so explicit and obvious.

2.8. Erlang interoperability

Because both Elixir and Erlang share the same bytecode, calling Erlang code doesn’t affect performance in any way. More important, this means you’re free to use any Erlang library with your Elixir code.

2.8.1. Calling Erlang functions from Elixir

The only difference is how the code is called. For example, you can generate a random number in Erlang like so:

1> random:uniform(123)
55


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":3},{\"line\":0,\"ch\":22}]]"}
!@%STYLE%@!

This function comes as part of the standard Erlang distribution. You can invoke the same Erlang function in Elixir with some syntactical tweaks:

iex> :random.uniform(123)
55


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":25}]]"}
!@%STYLE%@!

Notice the positions of the colon and dot in the two snippets. Those are the only differences!

There’s a minor caveat in Elixir when working with native Erlang functions—you can’t access documentation for Erlang functions from iex:

iex(3)> h :random
:random is an Erlang module and, as such, it does not have Elixir-style docs


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":17}]]"}
!@%STYLE%@!

Calling Erlang functions can be useful when Elixir doesn’t have an implementation available in the standard library. If you compare the Erlang standard library and that of Elixir, you may conclude that Erlang’s library has many more features. But if you think about it, Elixir gets everything for free!

2.8.2. Calling the Erlang HTTP client in Elixir

When Elixir is missing a feature I want, I usually check whether there’s an Erlang standard library function I can use before I search for third-party libraries. For example, I once wanted to build a web crawler in Elixir. One of the first steps in building a web crawler is having the ability to download a web page. This requires an HTTP client. Elixir doesn’t come with a built-in HTTP client—it doesn’t need to, because Erlang comes with one, aptly named httpc.[6]

Let’s say you want to download the web page for a certain programming language. You can go to the Erlang documentation[7] and find exactly what you need, as shown in figure 2.10.

7 Who am I kidding? In reality, I’d probably go to Stack Overflow first.

Figure 2.10. The httpc:request/1 Erlang documentation

First you need to start the inets application (it’s in the documentation), and then you make the actual request:

iex(1)> :inets.start
:ok
iex(2)> {:ok, {status, headers, body}} = :httpc.request 'http://www.elixir-lang.org'
{:ok,
 {{'HTTP/1.1', 200, 'OK'},
  [{'cache-control', 'max-age=600'}, {'date', 'Tue, 28 Oct 2014 16:17:24 GMT'},
   {'accept-ranges', 'bytes'}, {'server', 'GitHub.com'},
   {'vary', 'Accept-Encoding'}, {'content-length', '17251'},
   {'content-type', 'text/html; charset=utf-8'},
   {'expires', 'Tue, 28 Oct 2014 16:27:24 GMT'},
   {'last-modified', 'Tue, 21 Oct 2014 23:38:22 GMT'}],
  [60, 33, 68, 79, 67, 84, 89, 80, 69, 32, 104, 116, 109, 108, 62, 10, 60, 104,
   116, 109, 108, 32, 120, 109, 108, 110, 115, 61, 34, 104, 116, 116, 112, 58,
   47, 47, 119, 119, 119, 46, 119, 51, 46, 111, 114, 103, 47, 49, 57, 57, ...]}}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":20}],[{\"line\":2,\"ch\":8},{\"line\":2,\"ch\":75}],[{\"line\":2,\"ch\":75},{\"line\":2,\"ch\":84}]]"}
!@%STYLE%@!

2.8.3. One more thing...

Erlang has also a neat GUI front end called Observer that lets you inspect the Erlang virtual machine, among other things. Invoking it is simple:

iex(1)> :observer.start


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":23}]]"}
!@%STYLE%@!

Because you aren’t running any computationally intensive processes, you won’t see much action for now. Figure 2.11 will whet your appetite.

Figure 2.11. Screenshots from Observer

Observer is useful when it comes to seeing how much load the VM is taking and the layout of your supervision trees (you’ll learn about that in chapter 6). You can also see the data stored in the built-in database(s) that Erlang provides.

Sign in for more free preview time

2.9. Exercises

This was a pretty long chapter. Now it’s time to make sure you understood everything! Try the following exercises:

1.  Implement sum/1. This function should take in a list of numbers and return the sum of the list.

2.  Explore the Enum module and familiarize yourself with the various functions.

3.  Transform [1,[[2],3]] to [9, 4, 1] with and without the pipe operator.

4.  Translate crypto:md5("Tales from the Crypt"). from Erlang to Elixir.

5.  Explore the official Elixir “Getting Started” guide (http://elixir-lang.org/getting_started/1.html).

6.  Take a look at an IPV4 packet. Try writing a parser for it.

join today to enjoy all our content. all the time.
 

2.10. Summary

This concludes our whirlwind tour. If you’ve made it this far, give yourself a pat on the back. Don’t worry if you didn’t understand everything; the concepts will make more sense as you continue to read, and many of the programming constructs will be obvious once you see their applications. As a quick recap, here’s what you learned about in this chapter:

  • Elixir’s fundamental data types.
  • Guards and how they work with function clauses.
  • Pattern matching and how it leads to declarative code. We also looked at a few real-world examples of pattern matching.
  • Lists, which are another fundamental data structure. You saw how lists are represented internally in Elixir and how that facilitates recursion.
  • How Elixir and Erlang play nicely with each other.

In the next chapter, you’ll learn about the fundamental unit of concurrency in Elixir: the process. This is one of the features that makes Elixir vastly different from traditional programming languages.

sitemap
×

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage