Collection Types

The types we’ve seen so far are common in other programming languages. Now we’re getting into more exotic types, so we’ll go into more detail here.

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

Tuples

A tuple is an ordered collection of values. As with all Elixir data structures, once created a tuple cannot be modified.

You write a tuple between braces, separating the elements with commas.

 { 1, 2 } { ​:ok​, 42, ​"​​next"​ } { ​:error​, ​:enoent​ }

A typical Elixir tuple has two to four elements—any more and you’ll probably want to look at maps,, or structs,.

You can use tuples in pattern matching:

 iex>​ {status, count, action} = {​:ok​, 42, ​"​​next"​}
 {:ok, 42, "next"}
 iex>​ status
 :ok
 iex>​ count
 42
 iex>​ action
 "next"

It is common for functions to return a tuple where the first element is the atom :ok if there were no errors. Here’s an example (assuming you have a file called mix.exs in your current directory):

 iex>​ {status, file} = File.open(​"​​mix.exs"​)
 {:ok, #PID<0.39.0>}

Because the file was successfully opened, the tuple contains an :ok status and a PID, which is how we access the contents.

A common practice is to write matches that assume success:

 iex>​ { ​:ok​, file } = File.open(​"​​mix.exs"​)
 {:ok, #PID<0.39.0>}
 iex>​ { ​:ok​, file } = File.open(​"​​non-existent-file"​)
 **​ (MatchError) no match of right hand side value: {:error, :enoent}

The second open failed, and returned a tuple where the first element was :error. This caused the match to fail, and the error message shows that the second element contains the reason—enoent is Unix-speak for “file does not exist.”

Lists

We’ve already seen Elixir’s list literal syntax, [1,2,3]. This might lead you to think lists are like arrays in other languages, but they are not. (In fact, tuples are the closest Elixir gets to a conventional array.) Instead, a list is effectively a linked data structure.

Definition of a List

images/aside-icons/info.png A list may either be empty or consist of a head and a tail. The head contains a value and the tail is itself a list.

(If you’ve used the language Lisp, then this will all seem very familiar.)

As we’ll discuss in Chapter 7, Lists and Recursion, this recursive definition of a list is the core of much Elixir programming.

Because of their implementation, lists are easy to traverse linearly, but they are expensive to access in random order. (To get to the nth element, you have to scan through n–1 previous elements.) It is always cheap to get the head of a list and to extract the tail of a list.

Lists have one other performance characteristic. Remember that we said all Elixir data structures are immutable? That means once a list has been made, it will never be changed. So, if we want to remove the head from a list, leaving just the tail, we never have to copy the list. Instead we can return a pointer to the tail. This is the basis of all the list-traversal tricks we’ll cover in Chapter 7, Lists and Recursion.

Elixir has some operators that work specifically on lists:

 iex>​ [ 1, 2, 3 ] ++ [ 4, 5, 6 ] ​# concatenation
 [1, 2, 3, 4, 5, 6]
 iex>​ [1, 2, 3, 4] -- [2, 4] ​# difference
 [1, 3]
 iex>​ 1 ​in​ [1,2,3,4] ​# membership
 true
 iex>​ ​"​​wombat"​ ​in​ [1, 2, 3, 4]
 false

Keyword Lists

Because we often need simple lists of key/value pairs, Elixir gives us a shortcut. If we write

 [ ​name:​ ​"​​Dave"​, ​city:​ ​"​​Dallas"​, ​likes:​ ​"​​Programming"​ ]

Elixir converts it into a list of two-value tuples:

 [ {​:name​, ​"​​Dave"​}, {​:city​, ​"​​Dallas"​}, {​:likes​, ​"​​Programming"​} ]

Elixir allows us to leave off the square brackets if a keyword list is the last argument in a function call. Thus,

 DB.save record, [ {​:use_transaction​, true}, {​:logging​, ​"​​HIGH"​} ]

can be written more cleanly as

 DB.save record, ​use_transaction:​ true, ​logging:​ ​"​​HIGH"

We can also leave off the brackets if a keyword list appears as the last item in any context where a list of values is expected.

 iex>​ [1, ​fred:​ 1, ​dave:​ 2]
 [1, {:fred, 1}, {:dave, 2}]
 iex>​ {1, ​fred:​ 1, ​dave:​ 2}
 {1, [fred: 1, dave: 2]}