Run this notebook

Use Livebook to open this notebook and explore new ideas.

It is easy to get started, on your machine or the cloud.

Click below to open and run it in your Livebook at .

(or change your Livebook location)

# Control Flow Elixir offers four control flow structures: `if/unless`, `case`, `cond`, and `with`. ## `if/2` and `unless/2` If-else works as you'd expect. If your condition evaluates to `truthy` (not `nil` or `false`), the code block is executed and the result is returned. If the condition evaluates to `falsy` (`nil` or `false`), the `else` block is executed if it exists; otherwise, `nil` is returned. The reverse form of `if/2` is `unless/2`. If your `unless` condition evaluates to `truthy`, the `else` block is executed. If the condition is `falsy`, the first block is executed. It is common to choose `if` over `unless` because it doesn't make your brain hurt, unless you want to execute a side effect or throw an exception. ```elixir age = 17 result = if age >= 18 do :adult else :minor end IO.inspect(result, label: 1) unless age >= 18 do raise "A minor tries to access the system!" end ``` <!-- livebook:{"output":true} --> ``` 1: :minor ** (RuntimeError) A minor tries to access the system! ``` ## `case/2` The `case` statement is similar to `switch` in other languages. It allows you to state multiple clauses and tries to match your conditional against each clause. ```elixir active_status = :active fun = fn value -> case value do %{status: ^active_status} -> :active %{status: _other_status} -> :inactive %{age: nil} -> :age_missing %{age: age} when is_integer(age) and age >= 18 -> :adult %{age: age} when is_integer(age) -> :minor # An optional fallback which always matches. # Without it, the case statement would raise an exception if no clause matches. _ -> :default end end fun.(%{status: :active}) # => :active fun.(%{status: :foobar}) # => :inactive fun.(%{age: nil}) # => :age_missing fun.(%{age: 18}) # => :adult fun.(%{age: 17}) # => :minor fun.(nil) # => :default ``` ## `cond/1` The `cond` construct allows you to evaluate multiple clauses and return from the first that evaluates to `truthy`. It is especially useful if you need to execute a function in a clause. ```elixir adult? = fn age -> age >= 18 end fun = fn value -> cond do value == :active -> :active is_integer(value) && adult?.(value) -> :adult is_integer(value) -> :minor # The optional fallback clause must always evaluate to a truthy value. # If you don't give this and no other clause matches, Elixir raises an exception. true -> :default end end fun.(:active) # => :active fun.(18) # => :adult fun.(17) # => :minor fun.(nil) # => :default ``` ## `with/1` The `with/1` statement is useful to return early from a sequence of steps if one step fails. If you find yourself writing nested if-else or case statements, you probably want to use `with/1` instead. ```elixir adult? = fn age -> age >= 18 end details = %{name: "Peter", age: 32} # Instead of writing nested if- or case-statements like this: check_access = fn details -> case details do %{age: age} -> if adult?.(age) do :ok else {:error, :not_adult} end _ -> {:error, :age_missing} end end # Rather use one with-statement like this: check_access_refactored = fn details -> with %{age: age} <- details, true <- adult?.(age) do :ok else # Gets executed if any of the matches above fails. %{} -> {:error, :age_missing} false -> {:error, :not_adult} end end ``` To match inside the `else`-block isn't great though. What if two matches can return `false`? How would you know which match failed? It is good practice to move steps step into small helper functions. This allows you to return a specific error message depending on which step failed and it makes the `with`-statement more readable. It also makes it easy to see the "happy path" of your function and all its error cases. ```elixir defmodule RunElixir.Permissions do def check_access(details) do with {:ok, age} <- get_age(details), :ok <- check_adult(age) do :ok end end # Moving each step into a small helper function gives you the flexibility # to decide which error to return inside the function. defp get_age(details) do case details do %{age: age} when is_integer(age) -> {:ok, age} %{age: _age} -> {:error, :age_invalid} _ -> {:error, :age_missing} end end defp check_adult(age) do if age >= 18, do: :ok, else: {:error, :not_adult} end end RunElixir.Permissions.check_access(%{age: 32}) # => :ok RunElixir.Permissions.check_access(%{age: nil}) # => {:error, :age_invalid} RunElixir.Permissions.check_access(%{name: "Peter"}) # => {:error, :age_missing} RunElixir.Permissions.check_access(%{age: 17}) # => {:error, :not_adult} ```
See source

Have you already installed Livebook?

If you already installed Livebook, you can configure the default Livebook location where you want to open notebooks.
Livebook up Checking status We can't reach this Livebook (but we saved your preference anyway)
Run notebook

Not yet? Install Livebook in just a minute

Livebook is open source, free, and ready to run anywhere.

Run on your machine

with Livebook Desktop

Run in the cloud

on select platforms

To run on Linux, Docker, embedded devices, or Elixir’s Mix, check our README.

PLATINUM SPONSORS
SPONSORS
Code navigation with go to definition of modules and functions Read More ×