With clauses in Elixir

How it works

Have a look at the with clause shown below, it has two matches that must pass

  1. func_ok() must return :ok
  2. func_error() must return :error

It will then invoke the do end block that is IO.puts "printed" only if the two conditions above are met

with :ok <- func_ok(), :error <- func_error() do
  IO.puts "printed"

This is useful when for example you want to ensure the following conditions are met

  1. user is logged in
  2. the promo code entered is still valid
  3. the user is not blacklisted
  4. the item you are applying the promo code to can accept the discounts
with :ok <- login_user(),
     :valid <- check_promo_code(),
       :ok <- user_not_blacklisted(),
       :ok <- item_on_promo() do

A cool thing, you can assign variables too

with :ok <- :ok, :error <- :error, ans = 3 + 4 do
  IO.puts ans

It stops checking immediately a check fails

If in the process of executing the functions, one of then does not return the expected result, then the unmatching result is returned. For example,

If func_ok() below returned {:error, :unauthorised} then the func_error() will not be executed and the return value of the with clause will be {:error, :unauthorised}

with :ok <- func_ok(), :error <- func_error() do
  IO.puts "printed"

you can match failing clauses

if you do not want to return the failing return value then you can pattern match on the return value and have a custom return. This introduces the else block

with :ok <- func_ok(), :error <- func_error() do
  IO.puts "printed"
    {:error, :unauthorised} -> "Custom return value"

This time round if the func_ok() returns {:error, :unauthorised} it will be pattern matched on the else block.

What do you think will happen if func_error() returns :ok? It will actually blow up, why? Its because you have to match all the possible return values of all the expressions in the with block. So how can you make it work as expected? Add a second pattern on the else block 😄


Author: Sigu Magwa

Created: 2021-10-23 Sat 16:56