Skip to content

Commit 63fc8b6

Browse files
committed
Improve lists and tuples guides with more and simpler examples
1 parent 17b8b36 commit 63fc8b6

File tree

1 file changed

+27
-5
lines changed

1 file changed

+27
-5
lines changed

lib/elixir/pages/getting-started/lists-and-tuples.md

+27-5
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ iex> list ++ [4]
130130
[1, 2, 3, 4]
131131
```
132132

133-
Tuples, on the other hand, are stored contiguously in memory. This means getting the tuple size or accessing an element by index is fast. However, updating or adding elements to tuples is expensive because it requires creating a new tuple in memory:
133+
Tuples, on the other hand, are stored contiguously in memory. This means getting the tuple size or accessing an element by index is fast. On the other hand, updating or adding elements to tuples is expensive because it requires creating a new tuple in memory:
134134

135135
```elixir
136136
iex> tuple = {:a, :b, :c, :d}
@@ -139,9 +139,29 @@ iex> put_elem(tuple, 2, :e)
139139
{:a, :b, :e, :d}
140140
```
141141

142-
Note that this applies only to the tuple itself, not its contents. For instance, when you update a tuple, all entries are shared between the old and the new tuple, except for the entry that has been replaced. In other words, tuples and lists in Elixir are capable of sharing their contents. This reduces the amount of memory allocation the language needs to perform and is only possible thanks to the immutable semantics of the language.
142+
Note, however, the elements themselves are not copied. When you update a tuple, all entries are shared between the old and the new tuple, except for the entry that has been replaced. This rule applies to most data structures in Elixir. This reduces the amount of memory allocation the language needs to perform and is only possible thanks to the immutable semantics of the language.
143143

144-
Those performance characteristics dictate the usage of those data structures. One very common use case for tuples is to use them to return extra information from a function. For example, `File.read/1` is a function that can be used to read file contents. It returns a tuple:
144+
Those performance characteristics dictate the usage of those data structures. In a nutshell, lists are used when the number of elements returned may vary. Tuples have a fixed size. Let's see two examples from the `String` module:
145+
146+
```elixir
147+
iex> String.split("hello world")
148+
["hello", "world"]
149+
iex> String.split("hello beautiful world")
150+
["hello", "beautiful", "world"]
151+
```
152+
153+
The `String.split/2` function breaks a string into a list of strings on every whitespace character. Since the amount of elements returned depends on the input, we use a list.
154+
155+
On the other hand, `String.split_at/2` splits a string in two parts at a given position. Since it always returns two entries, regardless of the input size, it returns tuples:
156+
157+
```elixir
158+
iex> String.split_at("hello world", 3)
159+
{"hel", "lo world"}
160+
iex> String.split_at("hello world", 3)
161+
{"hello w", "orld"}
162+
```
163+
164+
It is also very common to use tuples and atoms to create "tagged tuples", which is a handy return value when an operation may succeed or fail. For example, `File.read/1` reads the contents of a file at a given path, which may or may not exist. It returns tagged tuples:
145165

146166
```elixir
147167
iex> File.read("path/to/existing/file")
@@ -150,9 +170,9 @@ iex> File.read("path/to/unknown/file")
150170
{:error, :enoent}
151171
```
152172

153-
If the path given to `File.read/1` exists, it returns a tuple with the atom `:ok` as the first element and the file contents as the second. Otherwise, it returns a tuple with `:error` and the error description. `File.read/1` returns a tuple, not a list, because it always contains two values. As we will soon learn, Elixir allows us to _pattern match_ on such shapes. Returning a list would not bring any advantages.
173+
If the path given to `File.read/1` exists, it returns a tuple with the atom `:ok` as the first element and the file contents as the second. Otherwise, it returns a tuple with `:error` and the error description. As we will soon learn, Elixir allows us to *pattern match* on tagged tuples and effortlessly handle both success and failure cases.
154174

155-
Most of the time, Elixir is going to guide you to do the right thing. For example, there is an `elem/2` function to access a tuple item but there is no built-in equivalent for lists:
175+
Given Elixir consistently follows those rules, the choice between lists and tuples get clearer as you learn and use the language. Elixir often guides you to do the right thing. For example, there is an `elem/2` function to access a tuple item:
156176

157177
```elixir
158178
iex> tuple = {:ok, "hello"}
@@ -161,6 +181,8 @@ iex> elem(tuple, 1)
161181
"hello"
162182
```
163183

184+
However, given you often don't know the number of elements in a list, there is no built-in equivalent for accessing arbitrary entries in a lists, apart from its head.
185+
164186
## Size or length?
165187

166188
When counting the elements in a data structure, Elixir also abides by a simple rule: the function is named `size` if the operation is in constant time (the value is pre-calculated) or `length` if the operation is linear (calculating the length gets slower as the input grows). As a mnemonic, both "length" and "linear" start with "l".

0 commit comments

Comments
 (0)