Chapter 2. Intro to Elixir

This chapter covers

  • The basics of the Elixir programming language
  • Creating a module
  • Using built-in tools to help you work smarter

Now that you’ve learned a bit about the benefits Phoenix can bring to your web application development, you’ll need to know Elixir. Phoenix is a web framework that’s built on top of the Elixir language (which is built on the Erlang VM, known as BEAM). So what if you don’t know Elixir?

This chapter will get you up to speed on Elixir. If you already know Elixir, you can probably skip this chapter entirely. If you don’t know Elixir or need a refresher, this chapter will cover just enough Elixir to get you going. Some concepts won’t be covered in this chapter—I’ll teach you about them later in the book when you need to know them. I’ll also point you to more resources at the end of the chapter, in case you’d like to dive deeper into the language and all its features.

2.1. The basics

Before we begin writing Elixir, I’ll show you a quick example of what a module might look like (a module can be thought of as a collection of functions). Have you heard of the “fizz buzz” programming exercise? This is the basic idea:

  1. Take any series of numbers, one at a time.
  2. If the number is divisible by 3, print “fizz.”
  3. If the number is divisible by 5, print “buzz.”
  4. If the number is divisible by 3 and 5, print “fizzbuzz.”
  5. Otherwise, print the number.

Listings 2.1, 2.2, and 2.3 show different variations of how you could implement this in Elixir. If you don’t know Elixir, don’t worry—just take a look at the syntax and see if you can recognize some features that are available in any other languages you know.

Listing 2.1 is an example of function overloading (defining multiple functions with the same name). Elixir will run the first function definition that matches what it’s given in terms of argument count and situation (such as when rem(num, 5) == 0, which means “when the remainder of num divided by 5 equals 0”).

Listing 2.1. FizzBuzz implementation 1
defmodule FizzBuzz do
  def go(min, max) do                                           1
    min..max
    |> Enum.each(fn(num) -> go(num) end)
  end
  def go(num) when rem(num, 15) == 0, do: IO.puts "fizzbuzz"    2
  def go(num) when rem(num, 3)  == 0, do: IO.puts "fizz"
  def go(num) when rem(num, 5)  == 0, do: IO.puts "buzz"
  def go(num), do: IO.puts num
end

FizzBuzz.go(1, 100)

  • 1 There are five functions named “go” in this example: one expects two arguments, and four expect one argument
  • 2 Elixir knows which function to run based on which definition first matches the situation.

Listing 2.2 illustrates the basic use of cond. Inside the cond block, Elixir will execute the first condition that is true.

Listing 2.2. FizzBuzz implementation 2
defmodule FizzBuzz do
  def go(min, max), do: Enum.each(min..max, &(go(&1)))      1
  def go(num) do
    cond do
      rem(num, 3) == 0 && rem(num, 5) == 0 -> IO.puts "fizzbuzz"
      rem(num, 3) == 0 -> IO.puts "fizz"
      rem(num, 5) == 0 -> IO.puts "buzz"
      true -> IO.puts num                                   2
    end
  end
end

FizzBuzz.go(1, 100)

  • 1 Enum.each/2 enumerates over the collection of integers from min to max and runs the go function for each.
  • 2 If no preceding condition matches, this line is executed because true will always be true.

Finally, listing 2.3 uses the case statement and pattern matching to implement the exercise requirements.

Listing 2.3. FizzBuzz implementation 3
defmodule FizzBuzz do
  def go(min, max), do: Enum.each(min..max, &go/1)
  def go(num) do
    case {rem(num, 3), rem(num, 5)} do             1
      {0, 0} -> IO.puts "fizzbuzz"                 2
      {0, _} -> IO.puts "fizz"
      {_, 0} -> IO.puts "buzz"
      _ -> IO.puts num                             3
    end
  end
end

FizzBuzz.go(1, 100)

  • 1 Ensures two numbers are in a tuple (such as {0, 4})
  • 2 Pattern-matches on those numbers. The underscore (_) means you don’t care what is in this spot.
  • 3 Uses the underscore (_) to match any situation that hasn’t already been matched

Each of these listings showcases some neat things you can do with Elixir:

  • Module declaration (defmodule)
  • Named function declaration (def)
  • Anonymous function declaration (fn(x) -> ... end)
  • Function overloading (multiple go functions defined)
  • Guard clauses (when ...)
  • Pattern matching ({0, _} for example)
  • cond and case statements
  • Using functions from other modules (like the each function from the Enum module)
  • Various Elixir data types (Range, Tuple, Integer, Boolean)
  • The pipe operator (|>)

Again, if all this is foreign to you, don’t worry. You’ll understand them after you’ve digested this chapter.

If you haven’t yet installed Elixir, now is the time to do so (appendix A will guide you through the installation process).

2.1.1. IEx

Once Elixir is installed, you’ll have some new executables available, including IEx. In this chapter (and a few beyond), you’ll use an iex session. IEx stands for Interactive Elixir, and it’s a REPL (read-eval-print loop) for Elixir. A REPL takes user input, evaluates it, prints the result out to the user, and loops back around to take user input again. Figure 2.1 illustrates this loop.

Figure 2.1. A REPL takes user input, evaluates it, prints it out to the user, and loops back around to take user input again.

IEx will act as our Elixir playground as we get started. But don’t let that fool you—it’s a pretty powerful tool even for the most advanced Elixir users. Let’s fire it up and see what we get.

To start a new session, open your terminal program and type iex (or iex.bat in Windows). You’ll see something like this:

Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:8:8] [async-threads:1]
     [hipe] [dtrace]

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

There’s a lot of debugging information displayed here, and most of those numbers and terms won’t matter in our use of IEx. In fact, some of it may look a little bit different on your computer. Don’t worry about it too much. The two most important things to look at are the Erlang/OTP version (21 in this example) and the Elixir version (1.7.4 here) that you’re running. If you’re ever in need of assistance in online discussions, those two version numbers will likely be important to those who are assisting you.

Another thing to notice is that the output provides you with two hints:

  • press Ctrl+C to exit
  • type h() ENTER for help

When the time comes to close your iex session, press Ctrl-C on your keyboard. Once you do, you’ll see this:

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

For now, don’t worry about those options other than (c)ontinue. If you pressed Ctrl-C by accident or now regret your decision, you can press c to continue your iex session. If you indeed want to quit and move on to other things, press Ctrl-C a second time or a. Those will dump you back into your terminal.

The h() is for help. You can follow along by typing h() in your IEx session now.

Listing 2.4. Getting help from an IEx session
iex(1)> h()
                                  IEx.Helpers

Welcome to Interactive Elixir. You are currently seeing the documentation for
the module IEx.Helpers which provides many helpers to make Elixir's shell more
joyful to work with.

This message was triggered by invoking the helper h(), usually referred to as
h/0 (since it expects 0 arguments).

You can use the h/1 function to invoke the documentation for any Elixir
     module
or function:

    iex> h Enum                    1
    iex> h Enum.map
    iex> h Enum.reverse/1

You can also use the i/1 function to introspect any value you have in the
shell:

    iex> i "hello"

There are many other helpers available, here are some examples:

# --- snip ---                     2

  • 1 The documentation gives you pointers on how to use the documentation.
  • 2 I’ve truncated this output, but the rest of these helpers are good to use. Experiment in your IEx session.

Whoa, that’s quite a bit of text! But don’t get overwhelmed—all of it is very helpful. One of the great things about Elixir is its idea of documentation being a first-class citizen along with your code. We’ll get into code documentation in later chapters, but as a quick example, if you type h(clear/0), it will give you documentation on the clear/0 function.

That documentation is written just above the function declaration in the Elixir source code. Figure 2.2 is a screenshot of the source code for clear/0. You can see that the number of lines dedicated to documentation closely matches the number of lines of code for the function.

Figure 2.2. The source code and in-line documentation for clear/0

This is an example of a function with light documentation. The documentation can be extensive, sometimes running to dozens of lines with multiple examples. This makes reading Elixir source code files easy. It also makes discovering how to use new modules and functions easy!

2.1.2. Basic types

Now that you know how to get help and how to exit an IEx session, let’s play around with some of the basic building blocks of the Elixir language. There are several basic types in Elixir that you’ll need to know. Table 2.1 lists the type names and provides an example of each type. We’ll go into detail on each of these types later in this chapter.

Table 2.1. Basic Elixir types

Type

Examples

Integer 34
Float 387.936
Boolean true/false
String "Phoenix in Action"
Charlist 'Phoenix in Action'
Atom :phoenix_in_action
List [34, 387.936, false]
Tuple {34, 387.936, false}
Map %{name: "Geoffrey", location: "Oceanside, CA"}
Range 1..100

These are basic types in Elixir. If you enter these in IEx and don’t do any manipulation of the data in them, IEx will just echo back the result (which is the data itself). You can follow along with the following listing by typing the same things in your IEx session.

Listing 2.5. Using different data types
iex(3)> 34                     1
34                             2
iex(4)> 387.936
387.936
iex(5)> true
true
iex(6)> "Phoenix in Action"
"Phoenix in Action"
iex(7)> 'Phoenix in Action'
'Phoenix in Action'
iex(8)> :phoenix_in_action
:phoenix_in_action
iex(9)> [34, 387.936, false]
[34, 387.936, false]
iex(10)> {34, 387.936, false}
{34, 387.936, false}

  • 1 Each line is executed.
  • 2 The result is printed. In all of these examples, the results are the inputs themselves.

We’ll cover each of these types in more depth in this chapter, so there’s no need to memorize them now.

2.1.3. A small detour into functions

Before we go into the details of the basic types, let’s take a quick tour of functions in Elixir. Why the detour? Well, some of the things you’ll do while exploring the basic types will use functions, so you’ll need to know about them first.

There are two main types of functions in Elixir: named functions and anonymous functions. Most of the time, you’ll write and use named functions, but anonymous functions are still very helpful.

Named functions

Named functions in Elixir are always within the context or scope of a module. A named function can’t exist outside of a module.

You can think of a module as a collection of functions that are grouped together for a purpose. For example, you could group functions dealing with the manufacturing of shirts in a Factory.Shirt module. There’s nothing special about the name of a module—the functions inside it could be defined in any module in your application and they would work the same way.

To recall the structure of a function call, you can remember MFAmodule-function-arguments. The first thing you write in a function call is the module name, followed by the function name, and finally the arguments for that function. Figure 2.3 illustrates the different pieces that make up a function call in Elixir.

Figure 2.3. The module-function-arguments structure

A named function is called by its name, and you can provide it with the arguments it expects. This code calls the is_integer function inside the Kernel module:

iex(1)> Kernel.is_integer(3)
true

This example calls the length function inside the Kernel module:

iex(1)> Kernel.length([1, 2, 3])
3

One thing to note is that Kernel functions are available in most situations without explicitly typing Kernel. This makes these functions available almost everywhere (and we’ll discuss how you can make your module functions act similarly later in the book). For example, these two calls are exactly the same as the previous two:

iex(1)> is_integer(3)
true

iex(2)> length([1, 2, 3])
3

Some special forms are also available. These include +, -, *, /, ==, !=, and so on. There are quite a few of them. These special forms can be used everywhere and are actually function calls, but you can call them a bit differently, thanks to some syntactic sugar:[1]

1

This is a good example of prefix notation versus infix notation. Kernel.+(1,3) is the normal prefix notation, but Elixir provides 1 + 3 with infix notation. Simply put, infix notation places the function call between the two arguments. Not very many functions have infix notation in Elixir, and you’ll rarely write one of your own. But sometimes it’s nice to know the correct terms for things.

iex(1)> 1 + 3
4

iex(2)> Kernel.+(1, 3)
4

Both of the preceding forms call directly to Erlang’s implementation of +. You can also use Erlang’s functions directly at any time:

iex(3)> :erlang.+(1, 3)
4

The :erlang syntax is special for Erlang, and you won’t be using it much. If you’d like to explore the different Kernel functions available, take a look at the source code for the Kernel module. It’s not as scary as it sounds. In fact, the vast majority of the file consists of documentation and examples.

Anonymous functions

Anonymous functions are functions that aren’t called by a name. They can exist outside of a module, unlike named functions. You can bind them to variable names that you can use to call the functions later (with a slightly different syntax). You can think of an anonymous function as a simple (or complex) piece of data transformation that you may or may not need to repeat.

For example, suppose you’re making sandwiches at a deli and you need to repeat the same process over and over again. If you had to write the sequence in code, you could write out each step every time an order came in. Or you could store the standard steps in a function and use that function every time an order came in. Then it wouldn’t matter how many orders you received—your code could handle the extra business. In pseudocode, it might look like the following listing.

Listing 2.6. Pseudocode for making a sandwich
for each order received:
  on a plate:
    add bread
    add mustard
    add turkey
    add cheese
    add tomato
    add lettuce
    add bread
  return plate to customer

Explicitly adding every ingredient creates a lot of repetition. Let’s slowly build an Elixir implementation of these steps using anonymous functions. Figure 2.4 illustrates the pieces required to define an anonymous function.

Figure 2.4. Pieces of an anonymous function

The standard procedure inside on a plate: could be contained in a function. To define an anonymous function, you start with fn and add any variable names you’d like to accept in your function enclosed in parentheses and separated by a comma. In the add_ingredient function in listing 2.7, you accept two variables and name them plate and ingredient. The function body is then indicated by a “stabby arrow” (->) and is contained between it and an end declaration. The function body in listing 2.7 adds the new ingredient to the plate. Note that Elixir functions implicitly return the last computed result of the function body.

Listing 2.7. Creating an anonymous function and binding it to add_ingredient
iex(1)> add_ingredient = fn(plate, ingredient) -> Enum.concat(plate,
     [ingredient]) end                                               1
#Function<12.52032458/2 in :erl_eval.expr/5>

  • 1 Uses a List for the ingredient list

Let’s now take our knowledge of anonymous functions and expand the previous pseudocode so it’s a little more functional.

Listing 2.8. Using an anonymous function
for each order received:
  plate = []                                1
  plate = add_ingredient.(plate, :bread)
  plate = add_ingredient.(plate, :mustard)
  plate = add_ingredient.(plate, :turkey)
  plate = add_ingredient.(plate, :cheese)
  plate = add_ingredient.(plate, :lettuce)
  plate = add_ingredient.(plate, :tomato)
  plate = add_ingredient.(plate, :bread)
  return plate to customer
end

  • 1 The plate starts out as an empty List of ingredients.

Calling anonymous functions is slightly different than calling named functions that are contained within a module. You must use the variable name you bound your function to (add_ingredient in the preceding example) followed by a dot and opening and closing parentheses. Inside the parentheses you put the values to pass to the function.

add_ingredient.(plate, ingredient)

Most of the time, your functions will require multiple lines and can be written as such. Whitespace, such as newlines, are generally not strictly enforced in Elixir.

iex(1)> add_ingredient = fn(plate, ingredient) ->
...(1)> Enum.concat(plate, [ingredient])
...(1)> end

You won’t be using many anonymous functions in this book. There are times when you will be writing them, such as using them to perform actions over a set of data, but you won’t be binding them to a variable very often. The following example shows an anonymous function that isn’t saved for later. Don’t worry too much about the syntax or what it’s doing—just try to recognize the anonymous function being used.

iex(1)> Enum.filter([1, 3, 5, 7, 9], fn(x) -> x < 6 end)
[1, 3, 5]

2.1.4. Data type details

I introduced the basic data types in Elixir, but we haven’t really dug into them very much. Let’s look at the types in some detail, along with some example usage.

Integers and floats

Numbers in Elixir are pretty straightforward, but there are a few things you’ll need to know before we dive into the other data types.

When writing a float that’s between -1 and 1, it must be preceded by the 0 (for example, 0.125, -0.87).

The division operator (/) always returns a float value, even when dividing two integers. Alternatively, div/2 does integer division, and rem/2 returns the remainder of a division. These functions (and many more in this section) are part of the Kernel module (Kernel.//2, Kernel.div/2, Kernel.rem/2). As you’ll recall, Kernel module functions are always available without explicitly preceding the function call with the module name.

iex(1)> 8 / 4
2.0
iex(2)> 8.0 / 5
1.6
iex(3)> div(8, 5)
1
iex(4)> rem(8, 5)
3

You can round a value by using the round/1 function. trunc/1 returns just the integer portion of a float value.

iex(1)> round(1.6)
2
iex(2)> trunc(1.6)
1
Booleans

Booleans simply represent a true or false value.

iex(1)> 1 == 5           1
false
iex(2)> is_integer(5)
true

  • 1 The double-equals (==) tests for equality.
Strings

As you’ve seen in the preceding examples, strings are enclosed in double quotes ("a string"). They’re not in single quotes ('not a string'), which are charlists (explained a bit later in this chapter).

Because Elixir supports UTF-8/Unicode by default, strings are stored as UTF-8 encoded binaries, where each character is stored as its Unicode bytes. As a result, Elixir considers strings a binary type.

Here are a couple of examples of working with strings:

iex(1)> is_binary("Phoenix in Action")
true

iex(2)> "Phoenix" <> " in " <> "Action"       1
"Phoenix in Action"

  • 1 Uses the <> operator to concatenate (join) strings

For longer, multiline strings (like documentation), it may be easier to define them with three double-quotes ("""), known as a heredoc. Everything inside the opening and closing """ will be retained, including spacing and line feeds.

iex(3)> haiku = """
...(3)> Build web apps for fun, profit
...(3)> Phoenix in Action
...(3)> Learn the things you need to know
...(3)> """
"Build web apps for fun, profit
Phoenix in Action
Learn the things you need
     to know
"                   1

  • 1 is the ASCII character for a newline.

Strings can also be interpolated with #{ }. Interpolation prints the string value of the code or variable contained within the curly braces.

iex(4)> subject = "Phoenix"
"Phoenix"

iex(5)> "#{subject} in Action"
"Phoenix in Action"

Further information can be found in Elixir’s String documentation: https://hexdocs.pm/elixir/String.html.

Charlists

The charlist is one type you need to pay special attention to because it has a gotcha. In Elixir, strings must be denoted using double quotes ("), whereas charlists are denoted using single quotes ('). A charlist is actually a special kind of list—it’s a list of ASCII number values that represent the characters, rather than being a list of the characters themselves. This may sound like a minute distinction, but as you’ll see in the following listing, this can lead to some funky and unexpected results.

Listing 2.9. Discovering charlist gotchas
iex(1)> 'Geo'
'Geo'

iex(2)> i('Geo')                           1
Term
  'Geo'
Data type
  List                                     2
Description
  This is a list of integers that is printed as a sequence of characters
  delimited by single quotes because all the integers in it represent valid
  ASCII characters. Conventionally, such lists of integers are referred to as
  "charlists" (more precisely, a charlist is a list of Unicode codepoints,
  and ASCII is a subset of Unicode).
Raw representation
  [71, 101, 111]                           3
Reference modules
  List
Implemented protocols
  IEx.Info, Collectable, Enumerable, Inspect, List.Chars, String.Chars

iex(3)> [71, 101, 111]                     4
'Geo'

iex(4)> {71, 101, 111}                     5
{71, 101, 111}

  • 1 Uses the i/1 function to inspect information about the passed value (‘Geo’)
  • 2 Elixir sees the ‘Geo’ term as a list data type because of the single quotes.
  • 3 Elixir informs you that, internally, ‘Geo’ is represented by the list [71, 101, 111].
  • 4 What happens if you use that raw data? What will Elixir echo back to you?
  • 5 Surrounding these numbers in curly brackets {} gives you a tuple rather than a list. Tuples don’t have the same issue as lists or charlists.

If you look at the end of the preceding listing, you can potentially imagine a confusing scenario. Suppose you have a function that returns a list containing the results of three calculations that return three integers (perhaps X, Y, Z coordinates). You run the function, and your iex session reports that the result is 'Geo'—it doesn’t look like a list at all, especially not one filled with three integers. But it is: 71, 101, and 111. In these cases, IEx is trying to be smart and helpful about displaying the data it received, but it turns out to be confusing, especially to beginners. Your data is still there as you’d expect it to be (those three integers representing your X, Y, Z coordinates), but IEx displays it in this way.

You won’t be using charlists much at all in this book. In fact, as an Elixir developer, I’ve rarely found the need for a charlist as opposed to a string. They’re mostly a holdover from Erlang. But you need to be aware that this particular gotcha exists.

Atoms

Atoms in Elixir are like symbols in other languages. Atoms start with a colon (:), they’re constants, and they are their own value. For example, :foo can never mean or be more than :foo—it can’t be rebound, its value (:foo) can’t change, and what you see is what you get. When I initially came across atoms, it seemed like there should be more to them ... but there isn’t. They really are that simple.

Atoms are used regularly in Elixir, especially in situations in which a function returns a status along with a value. Let’s take, for example, the Enum.fetch/2 function. Its documentation states this:

Finds the element at the given index (zero-based). Returns {:ok, element} if found, otherwise :error.

You can demonstrate this by using Enum.fetch/2 in an IEx session. First, you look for a value for a position that does exist in the list (the 2nd item in this list). Next, you try to fetch the value for a position that doesn’t exist in the list (the 15th item in the list). Notice that the return values contain atoms.

iex(1)> Enum.fetch([1, 2, 3, 4], 1)        1
{:ok, 2}

iex(2)> Enum.fetch([1, 2, 3, 4], 14)       2
:error

  • 1 Fetches index 1 of the list, which is 2.
  • 2 There is no index 14 in the list.

This pattern of using atoms to indicate success or error will be repeated in Elixir. You’ll see it often, so it’s a good idea to start writing a lot of your functions with the same pattern.

One caveat to atoms is that they’re the only Elixir data type that’s not garbage-collected. This means that if you create enough atoms, you can crash your system when it runs out of memory trying to allocate them. It’s a good idea to avoid using atoms for user-entered data, as an overload of them can be used as an attack vector against your server.

Lists

You can think of a list in Elixir as similar to an array in other languages, or as simply a list of items that may or may not be similar types. Depending on the language you’re most familiar with, it may even have similar syntax.

An Elixir list is a list of items contained in a single type (you’ll see in a moment that they’re more than that, though). Lists can contain any other Elixir types (even mixed types) and can even contain references to other lists.

iex(1)> list = [1, 2]
[1, 2]
iex(2)> [:numbers, "for example", list]
[:numbers, "for example", [1, 2]]

The interesting thing about lists in Elixir is that they’re linked lists. Each item in a list has an internal pointer to the next item in the list (see figure 2.5). You’ll never interact, see, or really know anything about that internal pointer—just trust that it’s there. Practically, this means that a number of typical operations are fast and memory-kind. It’s efficient to add items to the beginning of a list, but adding items to the end of a list can get slower as the list grows in size.

Figure 2.5. Lists are linked, meaning that each entry contains a pointer to the next item’s memory location.

The advantages and disadvantages of linked lists are a deep computer science topic that we won’t dive into, but if you’re interested in some additional reading on the topic, Wikipedia has a nice summary in its “Linked list” article: https://en.wikipedia.org/wiki/Linked_list.

Lists can be concatenated and subtracted from using ++ and --.

iex(1)> [1, 2] ++ ["three", :four]
[1, 2, "three", :four]
iex(2)> [1, 2, 3] -- [1, 3]
[2]

You’ll do a number of things with lists in this book, and you’ll often hear about the “head” and “tail” of a list—so much so that there are built-in functions and patterns you’ll use for both. The head of a list is, as you’ve probably guessed by now, the first item in the list. The tail is everything in the list that isn’t the head (in other words, the rest of the list). Figure 2.6 illustrates that each list has a head and a tail.

Figure 2.6. Each list has a head and a tail. You can use | to separate the head from the tail.

The | character is used to denote the break between the head and tail. I affectionately call it the “guillotine,” as it separates the head from the rest of the list.

iex(1)> [head | tail] = ["Phoenix", "in", "Action"]       1
["Phoenix", "in", "Action"]
iex(2)> head
"Phoenix"
iex(3)> tail
["in", "Action"]

  • 1 The | character denotes the break between the head and tail.

The hd function returns the head of a list (but, as in all of Elixir, it doesn’t modify the value sent to it).

iex(4)> hd(tail)
"in"
iex(5)> tail
["in", "Action"]

Internally, because Elixir uses linked lists, you can actually write out how Elixir “sees” them (a value acting as the head, followed by another list as the tail). The head of the following list is 1. The tail is itself a list consisting of 2 as its head and an empty list as its tail:

iex(1)> [1 | [2 | []]]
[1, 2]

You can use this pattern to easily prepend items to a list.

iex(1)> trees = ["Oak", "Pine"]
["Oak", "Pine"]
iex(2)> more_trees = ["Maple" | trees]
["Maple", "Oak", "Pine"]

There are lots of powerful list functions and great documentation online. If you want to dive further into lists, check out Elixir’s documentation: https://hexdocs.pm/elixir/List.html.

Tuples

Tuples are stylistically similar to lists, but instead of being surrounded by square brackets ([ and ]), tuples in Elixir are surrounded by curly braces ({ and }). They can also store an undetermined number of elements of differing types, like lists can. Under the hood, however, tuples are different. They are ordered and store their data contiguously in memory. Therefore, accessing any element in a tuple is a constant-time operation (unlike lists).

The Elixir documentation is a great resource for information on tuples. Here’s what it has to say:

Tuples are not meant to be used as a “collection” type (which is also suggested by the absence of an implementation of the Enumerable protocol for tuples): they’re mostly meant to be used as a fixed-size container for multiple elements. For example, tuples are often used to have functions return “enriched” values: a common pattern is for functions to return {:ok, value} for successful cases and {:error, reason} for unsuccessful cases.

As discussed in the section on functions, you’ll see the {:ok, value}/{:error, reason} pattern all through Elixir and Phoenix. Get used to it and learn to love it.

You can use the Kernel.elem/2 function to access a zero-based index of a tuple.

iex(1)> tuple = {:ok, 5, ["Phoenix", "in", "Action"]}
{:ok, 5, ["Phoenix", "in", "Action"]}
iex(2)> elem(tuple, 1)
5

For more information about tuples, check out Elixir’s documentation: https://hexdocs.pm/elixir/Tuple.html.

Maps

Maps are key/value stores in which each value can be bound to a unique key. In other languages, similar structures are called hashes or dictionaries. The key can be any data type, as can the values it’s related to. Ordering within a map is not deterministic, so you can’t rely on a map to return its pairs in the same order in which you wrote them. Maps are defined by enclosing their contents in %{}. The key of the entry is entered first, and its corresponding value is separated from the key with =>.

Here are some examples of using maps in an IEx session.

Listing 2.10. Playing with maps in IEx
iex(1)> map = %{:status => 200, "content" => "Hello world!"}
%{"content" => "Hello world!", :status => 200}

iex(2)> Map.fetch(map, "content")      1
{:ok, "Hello world!"}

  • 1 Uses the Map.fetch/2 function to access a map’s value for a given key

When you’re using atoms in a map as either the only keys or the last-passed keys, you can use a shorthand syntax: the atom name followed by a colon (:) and the value for that atom’s key. For example, %{a: 1} is functionally the same as %{:a => 1}, but it’s shorter to write and, in my humble opinion, nicer to look at. But you can’t use this syntax if you follow it with keys of a different type—Elixir will complain about this syntax, as you can see in the following listing. Elixir doesn’t know what to do with this syntax mixing, but if the shorthand is used in the last-passed keys, the syntax works.

Listing 2.11. Using the alternate atom key syntax for maps
iex(4)> %{a: 1, b: 2, c: 3}
{:b => 2, :a => 1, :c => 3}                          1

iex(5)> %{a: 1, "hello" => :world}                   2
** (SyntaxError) iex:5: syntax error before: "hello"

iex(6)> %{"hello" => :world, a: 1}                   3
%{:a => 1, "hello" => :world}

  • 1 The order of the keys in the returned value may not be the same as the order you specified. The order doesn’t matter.
  • 2 Fails because a: 1 is before “hello” => :world
  • 3 Works because the special a: 1 syntax is last

Apart from the Map.fetch/2 function used in listing 2.10, there are other, potentially more common ways to access a key’s value in a map. For example, you can use the map_name[key] shorthand to access a map’s value for the specified key. This works with any of the key types in Elixir.

Another way to access atom-based keys is by separating the variable name and the key to be retrieved with a period (.). But unlike the [key] style of access, this style of access only works with atom-based keys, as you can see in the error provided in the following snippet. Elixir specifically looked for the key :hello in the map.

Here are examples of both of those methods:

iex(1)> map = %{"hello" => :world, a: 1}
iex(2)> map[:a]
1

iex(3)> map["hello"]
:world

iex(4)> map.a
1

iex(5)> map.hello                               1
** (KeyError) key :hello not found in: %{:a => 1, "hello" => :world}

  • 1 You can’t use this type of getter with keys that are not atoms.

You’ll be using maps a lot in this book, so take some time to play with them in your IEx session. Also, read up on the power of maps and some of the included functions in the map documentation: https://hexdocs.pm/elixir/Map.html.

2.1.5. Back to modules and named functions

Most of your code won’t be simple anonymous functions. You’ll be organizing and reusing large portions of your codebase. To keep things organized, you can group functions that have things in common into modules. For example, if you had a group of functions that did mathematical calculations, you might group them together in a Math module. Let’s look at a few interesting aspects of modules.

All module definitions start with defmodule followed by the name of the module and do. Module names are InitCase, and you can also namespace modules with dot notation (.). For example, if you had a subset of math functions that dealt with mortgages, you could create a Math.Financial.Mortgage module. For now though, let’s keep the Math module simple:

defmodule Math do
  # ...
end

A function inside a module is defined with def, followed by the function name and any arguments it accepts. For an add function, you accept two arguments and assign them the variable names of a and b. Because Elixir implicitly returns the result of the last executed line of a function, you don’t have to explicitly return the value of adding a and b.

def add(a, b) do
  a + b
end

A shorthand version of the function definition can be helpful when your function does one thing or is a one-liner. The differences between the multiline and one-line function definitions are small: there’s an additional comma (,) after the argument collection and a colon (:) after do. Also note the lack of end:

def subtract(a, b), do: a - b

Functions can take any number of arguments, and if they take none, the parentheses can be omitted entirely.

def one, do: 1

Elixir allows you to define multiple functions with the same name, so how does it know which one to execute? It does pattern matching on the function signatures and uses the first one that matches. We’ll go deeper into pattern matching later in this chapter, but for now, think of it as looking for the function signature. A function signature is its name and the number of arguments it accepts (or it’s arity).

In the following listing, you have one even_numbers function that expects two arguments and another that accepts zero or one arguments—functions can have default argument values, specified by the \ characters. You can call even_numbers with a single number and have it find even numbers from 0 to the provided number, or you can call it with no arguments and have it default to returning even numbers from 0 to 10. Stylistically, functions with the same name are grouped together.

Listing 2.12. Demonstrating function overloading
defmodule Math do
  def even_numbers(min, max) do
    Enum.filter(min..max, fn(x) -> rem(x, 2) == 0 end)    1
  end
  def even_numbers(max \ 10) do
    even_numbers(0, max)                                  2
  end
end

  • 1 min..max creates a range of integers from min to max. rem/2 returns the remainder of dividing the first argument by the second.
  • 2 You can call even_numbers/2 inside the module it was defined in without using the module name.

Functions inside a module can call other functions inside the same module without using the module prefix. If there’s ever a cause for confusion over function names, you can use the full name of the function (Math.even_numbers) instead.

Functions that you need to call internally but that you don’t want exposed to the outside world, can be defined as private. You do this by using defp instead of def when defining the function.

defp internal_calculation(x), do: 42 + x

The following listing shows what our group of functions that deal with simple calculations might look like now.

Listing 2.13. Example Math module
defmodule Math do
  def add(a, b) do                                      1
    a + b
  end

  def subtract(a, b), do: a - b                         2

  def one, do: 1

  def even_numbers(min, max) do                         3
    Enum.filter(min..max, fn(x) -> rem(x, 2) == 0 end)
  end
  def even_numbers(max \ 10) do
    even_numbers(0, max)
  end

  defp internal_calculation(x), do: 42 + x              4
end

  • 1 Multiline function definition
  • 2 Single-line function definition
  • 3 Function overloading
  • 4 Private function definition

Let’s look at a few more points about modules before we move on.

2.1.6. alias

If you have a module name that’s deeply namespaced and you don’t want to type its long name every time, you can alias it, and then use only the last portion of the module name.

defmodule MyMortgage do
  alias Math.Financial.Mortgage

  def amount(args), do: Mortgage.calculate_amount(args)
end

Figure 2.7 illustrates what alias and import (covered next) do inside your module.

Figure 2.7. alias allows you to shorten the module name, whereas import allows you to forego it entirely.

2.1.7. import

You can further reduce typing and import the functions of a module into your module (as shown in listing 2.14). That will allow them to be called without prepending the module name. If you import a module without any arguments, all the public functions will be available. Alternatively, you can limit which functions are brought in by providing an only argument and a list of functions and their arity that you’d like to import. The following listing imports a fictional Math.Financial.Interest.rate/1 function. Once the functions are imported, you can call them without using their full module names.

Listing 2.14. Importing functions from external modules
defmodule MyMortgage do
  import Math.Financial.Mortgage # contains calculate_amount/1
  import Math.Financial.Interest, only: [rate: 1]

  def amount(args), do: calculate_amount(args)

  def interest_rate(args), do: rate(args)
end

2.2. Other idiomatic Elixir language features

Some of the discussion so far has been somewhat dry, but it has set the stage for your use of Elixir in the context of Phoenix. We’ve covered most of the basics, but there are some other cool, idiomatic, and useful Elixir features that you’ll be using extensively. Let’s take a look at those.

2.2.1. The pipe operator

One of my favorite things about the Elixir language syntax is the pipe operator (|>) and how it can clarify the flow of data from one function to another. When used correctly, it will make you wish every other language could and would implement it (and some are working on that).

The pipe operator looks benign and unassuming, but it’s very powerful. It allows you to pass one value into a function call as the first argument of that function call. With the pipe operator, the following two calls do the same thing:

Module.function(first_argument, second_argument)

first_argument |> Module.function(second_argument)

Why is that helpful? Why not just pass the value in directly as the first argument? Let’s look back at the deli from earlier in the chapter.

Listing 2.15. Deli pseudocode
for each order received:
  plate = []
  plate = add_ingredient.(plate, :bread)
  plate = add_ingredient.(plate, :mustard)
  plate = add_ingredient.(plate, :turkey)
  plate = add_ingredient.(plate, :cheese)
  plate = add_ingredient.(plate, :lettuce)
  plate = add_ingredient.(plate, :tomato)
  plate = add_ingredient.(plate, :bread)
  return plate
end

Look at all that rebinding of plate. Pretty ugly and repetitive, isn’t it? But notice how you wrote the add_ingredient/2 function: the first argument is expecting the current state of plate. Not only that, the function returns the new state of plate after adding ingredient. You can use the pipe operator to clean up this code greatly.

Listing 2.16. Deli pseudocode with the pipe operator
for each order received:
  new_plate()                      1
  |> add_ingredient.(:bread)
  |> add_ingredient.(:mustard)
  |> add_ingredient.(:turkey)
  |> add_ingredient.(:cheese)
  |> add_ingredient.(:lettuce)
  |> add_ingredient.(:tomato)
  |> add_ingredient.(:bread)
end

  • 1 new_plate sets up and returns a new, empty plate.

The plate variable is now gone! You bound no variables in the process of creating the sandwich. Instead, you take the result of one function and let the next function use it, passing the state of the plate down through the assembly instructions for the sandwich. Finally, because add_ingredient/2 returns the state of the new plate, that means you implicitly return the plate to the customer.

When writing functions, it’s normally worth the extra time to consider exactly how you want the data in your application to flow. With that in mind, you can arrange the functions as you’ve done here so that your Elixir code can read just like assembly instructions.

Get to know and love the pipe operator, because you’ll see it all over the place in Elixir, and you’ll be using it a lot. And believe me, you will love it.

2.2.2. Pattern matching

Think back on your personal experience with sandwiches. How do you recognize a sandwich? By the way it smells? Perhaps. By the way it sounds? Probably not. By the way it looks? Most likely, yes. Typically, sandwiches are made up of smaller building blocks, such as bread and meat, as in the deli examples. But you won’t typically recognize a sandwich by individually identifying all the smaller parts—normally you’ll recognize the pattern of those smaller objects that create a sandwich. That’s because your brain is wired to recognize patterns. Elixir is also wired to recognize patterns.

Suppose I want to verify that an object I’ve been given is a cheese sandwich. Someone hands it to me and says, “Here’s a cheese sandwich,” but I’m not sure if I can trust them. I’d use what I know about cheese sandwiches and use pattern recognition. Is it bread, cheese, and then bread again? If so, I’ve verified the pattern.

The same can be done with Elixir:

iex> cheese_sandwich = [:bread, :cheese, :bread]
[:bread, :cheese, :bread]
iex> [:bread, :cheese, :bread] = cheese_sandwich
[:bread, :cheese, :bread]

The first call in the preceding example sets up the variable with a list of atoms. The second call does the pattern check. The fact that I don’t receive an error on the second IEx call verifies that the pattern I told it to expect on the left side of the = sign is indeed the pattern on the right.

What would have happened if the patterns were different?

iex> [:bread, :cheese, :tomato, :bread] = cheese_sandwich
** (MatchError) no match of right hand side value: [:bread, :cheese, :bread]

I’d get a MatchError, which tells me that the pattern I told it to expect (with :tomato) is not what’s on the right side. This works for any Elixir type. Figure 2.8 illustrates how pattern matching works with this sandwich example.

Figure 2.8. Pattern matching compares the two sides of a call. If everything matches on both sides of the equal sign (=), it’s a successful match. If one side is different from the other, as in the bottom example, an error will be returned.

Beyond simple pattern verification with explicit values, you can give it a pattern with variable placeholders, and bind the unknown values to those placeholders. Suppose I knew this strange sandwich-giver gave me something between two pieces of bread, but I wasn’t sure what it was. I can verify what I know about the pattern and bind the unknown to a variable to learn what’s in between those slices of bread.

iex> unknown_sandwich = [:bread, :olive_loaf, :bread]
iex> [:bread, mystery_meat, :bread] = unknown_sandwich
[:bread, :olive_loaf, :bread]
iex> mystery_meat
:olive_loaf

I can use that mystery_meat variable later in my code (perhaps to decide whether or not to actually eat the strange sandwich).

As previously mentioned, one pattern you’ll see a lot in Elixir is {:ok, value} or {:error, reason}. This is an idiomatic pattern, because it’s easy to match on in your code, to decide whether to continue the operation or present an error to the user. Take, for example, the Map.fetch/2 function. It takes a map and a key and, if it finds that key in the given map, it will return that key’s value as {:ok, value}. If the given map doesn’t have the provided key, it will return :error.

I can use that knowledge to search my memory to see if the mystery meat is something I’ll eat. But first, let’s check to make sure things are working as expected. I love turkey sandwiches, so let’s verify that :turkey is true in the edible_meats map:

iex> edible_meats = %{turkey: true, chicken: true, ham: false, olive_loaf:
     false}
iex> {:ok, edible} = Map.fetch(edible_meats, :turkey)
{:ok, true}
iex> edible
true

I’ll eat the turkey! Now let’s use mystery_meat, which has the value of :olive_loaf, to see if I’ll eat that:

iex> {:ok, edible} = Map.fetch(edible_meats, mystery_meat)       1
{:ok, false}
iex> edible
false

  • 1 Checking the edible_meats map for the :olive_loaf key

So I’ll pass on the olive loaf sandwich. But what happens when I’m given a meat I’ve never encountered—one for which I have no listing in my map? I’d expect to get an error, right?

iex> {:ok, edible} = Map.fetch(edible_meats, :head_cheese)
** (MatchError) no match of right hand side value: :error

My pattern match failed because I expected {:ok, value} but instead got :error. Awesome! Elixir can tell you when an unexpected pattern is used.

Let’s write a little function using pattern matching that will help me decide whether I’ll eat a mystery sandwich. Here’s how I’ll make my decision (also illustrated in figure 2.9). When I receive a sandwich, I’ll do the following:

  1. Set up a map of acceptable meats.
  2. Use pattern matching to pull the mystery meat out of the list. (Yes, this means that this function only works if there’s exactly one thing between two slices of bread. You can expand on this if you’d like, but we’ll keep it simple for now.)
  3. See if that meat is in my list of known meats.
  4. If it is, and it’s edible, I’ll return “Yes, please!”
  5. If it’s in my list of known meats and I don’t like that particular meat, I’ll return “No thanks.”
  6. If I get an :error, which means the meat isn’t in my list of known meats, I’ll return “I don’t know what this is!”
Figure 2.9. Sandwich.accept?/1 decision diagram

The following listing shows the function.

Listing 2.17. Should you eat the mystery sandwich?
defmodule Sandwich do
  def accept?(sandwich) do
    edible_meats = %{turkey: true, ham: false, chicken: true, olive_loaf:
     false}
    [:bread, mystery_meat, :bread] = sandwich

    case Map.fetch(edible_meats, mystery_meat) do
      {:ok, edible} ->
        if edible, do: "Yes, please!", else: "No thanks."
      :error ->
        "I don't know what this is!"
    end
  end
end

Let’s use the new function:

iex> Sandwich.accept?( [:bread, :turkey, :bread] )
"Yes, please!"
iex> Sandwich.accept?( [:bread, :olive_loaf, :bread] )
"No thanks."
iex> Sandwich.accept?( [:bread, :head_cheese, :bread] )
"I don't know what this is!"

You’ll often reach for the case statement to match a variety of expected results in order to correctly respond to the result of a function.

There will be times when you won’t know what value will be in a given pattern, and you want to bind it but then use the value of that variable in a future binding. In the pattern matching we’ve looked at so far, that won’t quite work:

iex(1)> [:bread, mystery_meat, :bread] = [:bread, :turkey, :bread]
iex(2)> mystery_meat
:turkey

iex(3)> [:bread, mystery_meat, :bread] = [:bread, :ham, :bread]
iex(4)> mystery_meat
:ham

You might have expected an error in call 3 because you wanted to ensure you got the same sandwich twice, no matter what the mystery_meat was. Instead, you rebound mystery_meat to :ham. How can you pin the value of a variable in pattern matching like this?

You can use the pin operator (^)! With it, you can pin the value in a pattern match so that you can match on that variable’s value instead of rebinding it with the right-side value.

iex(1)> [:bread, mystery_meat, :bread] = [:bread, :turkey, :bread]
iex(2)> mystery_meat
:turkey

iex(3)> [:bread, ^mystery_meat, :bread] = [:bread, :ham, :bread]
** (MatchError) no match of right hand side value: [:bread, :ham, :bread]
iex(4)> mystery_meat
:turkey

This time, because you pinned the variable ^mystery_meat, you didn’t rebind it but instead attempted to match on its value. Because the right side of the pattern match didn’t match the left, you got a MatchError. Finally, you verify that your mystery_meat variable wasn’t rebound to a value of :ham and instead retains its original :turkey value.

Pattern-matching maps can be fun and very code-efficient. In the following example, you pattern-match the key of :bass from the band variable, which contains a map of band member names. If you find a :bass key, you ask Elixir to bind the value of that key to the variable of name. Finally, you verify that you indeed now have "Adam" as the value of name straight from the band map.

iex(7)> band = %{vocals: "Paul", guitar: "Dave", bass: "Adam", drums: "Larry"}
iex(8)> %{bass: name} = band
iex(9)> name
"Adam"

Sometimes you’ll want to make sure a pattern is matched, but you don’t really care about storing a value for later use, nor do you care even which value is in a particular position. For example, you’ve used the Map.fetch/2 function often in this chapter, and you’ll remember that it returns {:ok, value} on a successful match. For the times when you don’t want to store that value, you can use the underscore (_) character to let Elixir know you expect something in that position, but you don’t really care what.

iex(1)> {:ok, _} = Map.fetch(%{b: 2}, :b)
{:ok, 2}
iex(2)> {_, _} = Map.fetch(%{c: 3}, :c)
{:ok, 3}

You can also prepend variable names with an underscore (_) to give the value an identifier but also let Elixir know that you don’t expect to use that variable’s value anywhere in your code.

iex(1)> {:ok, _name} = Map.fetch(%{vocals: "Paul"}, :vocals)

Pattern matching is not only incredibly useful and fun, it’s used quite often in Elixir code. You’ll also write a number of functions that use pattern matching in Phoenix. Take some time to play with pattern matching in Elixir.

2.2.3. Using IEx.Helpers.v/1

Even though I’ve used IEx for quite a while now, I have times when I’ve forgotten to capture the output of a long command, or I want to go back in my history to a different function’s result. IEx provides a nice helper function named v to help you out. You may have noticed the number following the iex in your IEx sessions (for example, iex(3)>). This is when those numbers will change from being visual noise to being potentially very helpful.

When it’s called without an argument, (v()), v/1 will return the last result again. When passed an argument, (v(3)), v/1 will return the result of the numbered past IEx expression (in this example’s case, 3; modify your call accordingly). Let’s take a look at some examples:

iex(1)> 1 + 5
6

iex(2)> six = v()               1
6
iex(3)> six + 3000
3006

iex(4)> (0.0056283568467 * 100_000) |> round()
563

iex(5)> v() + v(3)              2
3569

  • 1 Uses v() to return the value of the previous result
  • 2 Uses v(3) to return the result of a specific previous expression

You won’t be using this helper function at all in your modules, but it can be incredibly useful as you’re learning or experimenting with Elixir in an IEx session, or even when you’re debugging your code, trying to figure out exactly how things work.

If you’d like to dive deeper into learning Elixir, several great resources are listed in appendix B.

Summary

  • You can use IEx to practice, experiment, and learn Elixir in an interactive REPL. This is an easy way to get to know the basics of the language.
  • Ask for help from Elixir itself by using the h/1 IEx helper function.
  • Create named functions inside a module. Anonymous functions can be bound to a variable outside of a module for later use.
  • Use modules to keep your code together in sensible groupings of functions.
  • Take advantage of Elixir’s pipe operator (|>). It’s incredibly useful and can make your code read more like a sentence describing how your data will be transformed from one function through the next.
  • Utilize pattern matching in your code to quickly verify that a return value is in a pattern you expect, and to grab desired data directly from the structure of the return value. Pattern matching is also used by Elixir to determine which function to execute when multiple function definitions have the same name. The first one to match gets executed.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.196.217