6 Control Flow
6.1 if-else block
if condition do
...
else
...
end
condense into one line (notice the comma ,
after condition, similar to oneline function def fun(), do: expresion
)
if condition, do: true_value, else: false_value
6.3 case: case when with pattern matching
case expression do
-> return_1
pattern_1 -> return_2
pattern_2 ...
end
def max(a,b) do
case a >= b do
true -> a
false -> b
end
end
6.4 with: combine multiple clauses
Used to combine matching clauses. Let’s start with an example:
= %{width: 10, height: 15}
opts :ok, width} <- Map.fetch(opts, :width),
with {:ok, height} <- Map.fetch(opts, :height) do
{:ok, width * height}
{end
#> {:ok, 150}
If all clauses match, the do block is executed, returning its result. Otherwise the chain is aborted and the non-matched value is returned:
= %{width: 10}
opts :ok, width} <- Map.fetch(opts, :width),
with {:ok, height} <- Map.fetch(opts, :height) do
{:ok, width * height}
{end
#> :error
We can perform intermediate calculation during each pattern matching during with
, and the matched pattern won’t affect global state
= nil
width = %{width: 10, height: 15}
opts
:ok, width} <- Map.fetch(opts, :width),
with {= width * 2, # intermediate calculation
double_width :ok, height} <- Map.fetch(opts, :height) do
{:ok, double_width * height}
{end
#> {:ok, 300}
width#> nil
Alternative way of using comprehension with pattern matching
for %{width: width, height: height} <- [opts] do
* height
width end
#> [150]
6.5 comprehensions
In Elixir, it is common to loop over an Enumerable, often filtering out some results and mapping values into another list. Comprehensions are syntactic sugar for such constructs: they group those common tasks into the for special form.
For example, we can map a list of integers into their squared values:
> for n <- [1, 2, 3, 4], do: n * n
iex#> [1, 4, 9, 16]
A comprehension is made of three parts: generators, filters, and collectables. Comprension is most useful when multiple enumerables are involved and Enum.map
can’t do its job.
= for x <- 1..9, y <- 1..9, # generaotr
multiplication_table >= y, # filter
x into: %{} do # collectable
*y}
{{x, y}, xend
multiplication_table# %{
# {8, 5} => 40,
# {8, 6} => 48,
# {5, 2} => 10,
# {6, 5} => 30,
# {1, 1} => 1,
# {9, 6} => 54,
# {4, 3} => 12,
# ...
# {9, 7} => 63,
# {5, 1} => 5,
# {2, 2} => 4,
# {4, 4} => 16,
# {7, 4} => 28,
# {6, 3} => 18,
# {5, 4} => 20,
# {9, 9} => 81
# }
As you can see, when multiple generators are used, the result is a cartesian product of the enumerables.
= ~w[James John Patricia]
names = ~w[Johnson Smith Williams]
surnames
for name <- names,
<- surnames do
surname "#{name} #{surname}"
end
# => [
# => "James Johnson",
# => "James Smith",
# => "James Williams",
# => "John Johnson",
# => "John Smith",
# => "John Williams",
# => "Patricia Johnson",
# => "Patricia Smith",
# => "Patricia Williams"
# => ]
We can also use pattern matching in a generator to do filtering. If the left side of the generator does not match, then the for-loop will just ignore that value and continue to process.
= [
people name: "John", active: true},
%{name: "Patricia", active: false}
%{
]for %{active: true, name: name} <- people do
nameend
# => ["John"]
We can also use pattern matching alongside with the individual looping element, suppose we need to loop through the following two list for state “CA” and get joined maps that looks like %{id, state, description, address}
:
= [
propositions id: 1, state: "CA", description: "highway bond"},
%{id: 2, state: "WA", description: "Fuel Tax"}
%{
]
= [
locations id: 1, state: "CA", address: "123 Main St"},
%{id: 2, state: "WA", address: "321 Main St"}
%{
]
for x = %{state: stateA} <- propositions,
= %{state: stateB} <- locations,
y === "CA" && stateB === "CA" do
stateA Map.put(x, :address, y.address)
end
address: "123 Main St", description: "highway bond", id: 1, state: "CA"}] [%{
A shorter syntax using pattern matching to filter directly
= "CA"
state
for x = %{state: ^state} <- propositions,
= %{state: ^state} <- locations, do
y Map.put(x, :address, y.address)
end