Wi

With in Elixir

3 exercises

About With

The special form with provides a way to focus on the "happy path" of a series of potentially failing steps and deal with the failures later.

Let's say that you want to validate a username with several checks. You might reach out for case:

case check_ascii(username) do
  {:ok, username} ->
    case check_starts_with_letter(username) do
      {:ok, username} ->
        {:ok, username}

      {:error, "usernames may only start with a letter"} = err ->
        err
    end

  {:error, "usernames may only contain ascii letters"} = err ->
    err
end

It might be readable now, but if you add more nested checks, it's going to get messier. In this situation, use with:

with {:ok, username} <- check_ascii(username),
     {:ok, username} <- check_starts_with_letter(username),
     {:ok, username} <- check_not_a_joke_name(username),
     {:ok, username} <- check_if_available(username) do
  {:ok, "#{username} is a fine username and you deserve it!"}
end

At each step, if a clause matches, the chain will continue until the do block is executed. If one match fails, the chain stops and the non-matching clause is returned.

You can use guards in the chain, as well as the = operator for regular matches.

with {:ok, id} <- get_id(username),
     {:ok, avatar} when is_bitstring(avatar) <- fetch_avatar(id),
     avatar_size = bit_size(avatar),
     {:ok, image_type} <- check_valid_image_type(avatar) do
  {:ok, image_type, avatar_size, avatar}
end

Finally, you have the option of using an else block to catch failed matches and modify the return value.

with {:ok, id} <- get_id(username),
     {:ok, avatar} when is_bitstring(avatar) <- fetch_avatar(id),
     avatar_size = bit_size(avatar),
     {:ok, image_type} <- check_valid_image_type(avatar) do
  {:ok, image_type, avatar_size, avatar}
else
  :not_found ->
    {:error, "invalid username"}

  {:error, "not an image"} ->
    {:error, "avatar associated to #{username} is not an image"}

  err ->
    err
end

The else is like a case statement for any mismatched value of the with clause. The first value to match will be used.

Edit via GitHub The link opens in a new window or tab

Learn With