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.2 cond: multiple if-else blocks

cond do
  expression_1 -> return_1
  expression_2 -> return_2
  ...
end

6.3 case: case when with pattern matching

case expression do
  pattern_1 -> return_1
  pattern_2 -> return_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:

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
#> :error

We 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
#> nil

Alternative 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