6 Control Flow
6.1 if-else block
if condition do
...
else
...
endcondense into one line (notice the comma , after condition, similar to oneline function def fun(), do: expresion)
if condition, do: true_value, else: false_value6.3 case: case when with pattern matching
case expression do
pattern_1 -> return_1
pattern_2 -> return_2
...
enddef max(a,b) do
case a >= b do
true -> a
false -> b
end
end6.4 with: combine multiple clauses
Used to combine matching clauses. Let’s start with an example:
opts = %{width: 10, height: 15}
with {:ok, width} <- Map.fetch(opts, :width),
{: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:
opts = %{width: 10}
with {:ok, width} <- Map.fetch(opts, :width),
{:ok, height} <- Map.fetch(opts, :height) do
{:ok, width * height}
end
#> :errorWe can perform intermediate calculation during each pattern matching during with, and the matched pattern won’t affect global state
width = nil
opts = %{width: 10, height: 15}
with {:ok, width} <- Map.fetch(opts, :width),
double_width = width * 2, # intermediate calculation
{:ok, height} <- Map.fetch(opts, :height) do
{:ok, double_width * height}
end
#> {:ok, 300}
width
#> nilAlternative way of using comprehension with pattern matching
for %{width: width, height: height} <- [opts] do
width * height
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:
iex> for n <- [1, 2, 3, 4], do: n * n
#> [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.
multiplication_table = for x <- 1..9, y <- 1..9, # generaotr
x >= y, # filter
into: %{} do # collectable
{{x, y}, x*y}
end
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.
names = ~w[James John Patricia]
surnames = ~w[Johnson Smith Williams]
for name <- names,
surname <- surnames do
"#{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
name
end
# => ["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,
y = %{state: stateB} <- locations,
stateA === "CA" && stateB === "CA" do
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
state = "CA"
for x = %{state: ^state} <- propositions,
y = %{state: ^state} <- locations, do
Map.put(x, :address, y.address)
end