Skip to content

Commit 004308f

Browse files
committed
Provide guidelines around control flow
1 parent 87e090e commit 004308f

File tree

3 files changed

+77
-54
lines changed

3 files changed

+77
-54
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3865,9 +3865,13 @@ defmodule Kernel do
38653865
Provides an `if/2` macro.
38663866
38673867
This macro expects the first argument to be a condition and the second
3868-
argument to be a keyword list. Similar to `case/2`, any assignment in
3869-
the condition will be available on both clauses, as well as after the
3870-
`if` expression.
3868+
argument to be a keyword list. Generally speaking, Elixir developers
3869+
prefer to use pattern matching and guards in function definitions and
3870+
`case/2`. However, `if/2` is valuable for logical expressions that
3871+
cannot be written within the mechanisms found in patterns and guards.
3872+
3873+
Similar to `case/2`, any assignment in the condition will be available
3874+
on both clauses, as well as after the `if` expression.
38713875
38723876
## One-liner examples
38733877
@@ -3899,7 +3903,8 @@ defmodule Kernel do
38993903
baz
39003904
end
39013905
3902-
In order to compare more than two clauses, the `cond/1` macro has to be used.
3906+
If you find yourself nesting conditionals inside conditionals,
3907+
consider using `cond/1`.
39033908
"""
39043909
defmacro if(condition, clauses) do
39053910
build_if(condition, clauses)

lib/elixir/lib/kernel/special_forms.ex

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,6 +1887,11 @@ defmodule Kernel.SpecialForms do
18871887
@doc ~S"""
18881888
Matches the given expression against the given clauses.
18891889
1890+
`case/2` relies on pattern matching and guards to choose
1891+
which clause to execute. If your logic cannot be expressed
1892+
within patterns and guards, consider using `if/2` or `cond/1`
1893+
instead.
1894+
18901895
## Examples
18911896
18921897
case File.read(file) do
@@ -1917,6 +1922,9 @@ defmodule Kernel.SpecialForms do
19171922
end
19181923
#=> "This clause would match any value (x = 10)"
19191924
1925+
If you find yourself nesting `case` expressions inside
1926+
`case` expressions, consider using `with/1`.
1927+
19201928
## Variable handling
19211929
19221930
Note that variables bound in a clause do not leak to the outer context:
@@ -1976,17 +1984,20 @@ defmodule Kernel.SpecialForms do
19761984
Evaluates the expression corresponding to the first clause that
19771985
evaluates to a truthy value.
19781986
1987+
## Examples
1988+
1989+
The following example has a single clause that always evaluates
1990+
to true:
1991+
19791992
cond do
19801993
hd([1, 2, 3]) ->
19811994
"1 is considered as true"
19821995
end
19831996
#=> "1 is considered as true"
19841997
1985-
Raises an error if all conditions evaluate to `nil` or `false`.
1998+
If all clauses evaluate to `nil` or `false`, `cond` raises an error.
19861999
For this reason, it may be necessary to add a final always-truthy condition
1987-
(anything non-`false` and non-`nil`), which will always match.
1988-
1989-
## Examples
2000+
(anything non-`false` and non-`nil`), which will always match:
19902001
19912002
cond do
19922003
1 + 1 == 1 ->
@@ -1998,6 +2009,9 @@ defmodule Kernel.SpecialForms do
19982009
end
19992010
#=> "This will"
20002011
2012+
2013+
If your `cond` has two clauses, and the last one falls back to
2014+
`true`, you may consider using `if/2` instead.
20012015
"""
20022016
defmacro cond(clauses), do: error!([clauses])
20032017

lib/elixir/pages/getting-started/case-cond-and-if.md

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -67,51 +67,10 @@ iex> case :ok do
6767

6868
The documentation for the `Kernel` module lists all available guards in its sidebar. You can also consult the complete [Patterns and Guards](../references/patterns-and-guards.md#guards) reference for in-depth documentation.
6969

70-
## cond
71-
72-
`case` is useful when you need to match against different values. However, in many circumstances, we want to check different conditions and find the first one that does not evaluate to `nil` or `false`. In such cases, one may use `cond`:
73-
74-
```elixir
75-
iex> cond do
76-
...> 2 + 2 == 5 ->
77-
...> "This will not be true"
78-
...> 2 * 2 == 3 ->
79-
...> "Nor this"
80-
...> 1 + 1 == 2 ->
81-
...> "But this will"
82-
...> end
83-
"But this will"
84-
```
85-
86-
This is equivalent to `else if` clauses in many imperative languages - although used less frequently in Elixir.
87-
88-
If all of the conditions return `nil` or `false`, an error (`CondClauseError`) is raised. For this reason, it may be necessary to add a final condition, equal to `true`, which will always match:
89-
90-
```elixir
91-
iex> cond do
92-
...> 2 + 2 == 5 ->
93-
...> "This is never true"
94-
...> 2 * 2 == 3 ->
95-
...> "Nor this"
96-
...> true ->
97-
...> "This is always true (equivalent to else)"
98-
...> end
99-
"This is always true (equivalent to else)"
100-
```
101-
102-
Finally, note `cond` considers any value besides `nil` and `false` to be true:
103-
104-
```elixir
105-
iex> cond do
106-
...> hd([1, 2, 3]) ->
107-
...> "1 is considered as true"
108-
...> end
109-
"1 is considered as true"
110-
```
111-
11270
## if/unless
11371

114-
Besides `case` and `cond`, Elixir also provides `if/2` and `unless/2`, which are useful when you need to check for only one condition:
72+
73+
`case` builds on pattern matching and guards to destructure and match on certain conditions. However, patterns and guards are limited only to certain expressions which are optimized by the compiler. In many situations, you need to write conditions that go beyond what can be expressed with `case`. For those, `if/2` (and `unless/2`) are useful alternatives:
11574

11675
```elixir
11776
iex> if true do
@@ -126,7 +85,7 @@ nil
12685

12786
If the condition given to `if/2` returns `false` or `nil`, the body given between `do`-`end` is not executed and instead it returns `nil`. The opposite happens with `unless/2`.
12887

129-
They also support `else` blocks:
88+
They also support `else` blocks (although using `else` with `unless` is generally discouraged):
13089

13190
```elixir
13291
iex> if nil do
@@ -167,5 +126,50 @@ iex> x = if true do
167126
>
168127
> An interesting note regarding `if/2` and `unless/2` is that they are implemented as macros in the language: they aren't special language constructs as they would be in many languages. You can check the documentation and their source for more information.
169128
170-
We have concluded the introduction to the most fundamental control-flow constructs in Elixir. Now
171-
let's learn where code and data meet with anonymous functions.
129+
If you find yourself nesting several `if/2` blocks, you may want to consider using `cond/1` instead. Let's check it out.
130+
131+
## cond
132+
133+
If you need to check across several conditions and find the first one that does not evaluate to `nil` or `false`, `cond/1` is a useful construct:
134+
135+
```elixir
136+
iex> cond do
137+
...> 2 + 2 == 5 ->
138+
...> "This will not be true"
139+
...> 2 * 2 == 3 ->
140+
...> "Nor this"
141+
...> 1 + 1 == 2 ->
142+
...> "But this will"
143+
...> end
144+
"But this will"
145+
```
146+
147+
This is equivalent to `else if` clauses in many imperative languages - although used less frequently in Elixir.
148+
149+
If all of the conditions return `nil` or `false`, an error (`CondClauseError`) is raised. For this reason, it may be necessary to add a final condition, equal to `true`, which will always match:
150+
151+
```elixir
152+
iex> cond do
153+
...> 2 + 2 == 5 ->
154+
...> "This is never true"
155+
...> 2 * 2 == 3 ->
156+
...> "Nor this"
157+
...> true ->
158+
...> "This is always true (equivalent to else)"
159+
...> end
160+
"This is always true (equivalent to else)"
161+
```
162+
163+
Similar to `if/2` and `unless/2`, `cond` considers any value besides `nil` and `false` to be true:
164+
165+
```elixir
166+
iex> cond do
167+
...> hd([1, 2, 3]) ->
168+
...> "1 is considered as true"
169+
...> end
170+
"1 is considered as true"
171+
```
172+
173+
## Summing up
174+
175+
We have concluded the introduction to the most fundamental control-flow constructs in Elixir. Generally speaking, Elixir developers prefer pattern matching and guards, using `case` and function definitions (which we will explore in future chapters). When your logic cannot be expressed within patterns and guards, you may consider `if/2`, falling back to `cond/1` when there are several conditions to check.

0 commit comments

Comments
 (0)