Beginner's Track: With
or
Within You Without You 

Before we get with it...

  • In Elixir, the = operator is actually called the match operator
  • A common example you may see:

A quick pattern matching review

iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13

More pattern matching...

iex> x = 1
  1
iex> x
  1
iex> 1 = x
  1
iex> 2 = x
  ** (MatchError) no match of right hand side value: 1


iex> {:ok, result} = {:ok, 13}
  {:ok, 13}
iex> result
  13

Almost `with` it...A quick review of pipes

  • I'm hoping you have some experience with Unix pipes as these are similar.  
  • From the docs, "The `|>` symbol...is the pipe operator:
    • it takes the output from the expression on its left side and passes it as the first argument to the function call on its right side." [4]
  • They allow the ability to "stream" from one operation to the next.

Pipes example

1..500 |> 
  Enum.map(fn x -> x / 2 end) |> 
  Enum.filter(fn x -> x > 200 end) |> 
  Enum.count

What is with?

  • The `with` operator is a control flow mechanism that allows a sequence of operations to return a value or the ability to break out of that sequence.
  • It allows you to match on differing results, enabling you (similar to pipes) build a functional sequence

Examples

Let's look at some examples from Jose:

with {:ok, x} <- ok(1),
     {:ok, y} <- ok(2),
     do: {:ok, x + y}
#=> {:ok, 3}

If they all match in the sequence, we're done.

 

with {:ok, x} <- ok(1),
     {:ok, y} <- error(2),
     do: {:ok, x + y}
#=> {:error, 2}

Here, because they don't, the sequence is aborted and an error is returned.

Why use with?

  • Error handling
def handle_request(request) do  
  with {:ok} <- validate_request(request),
       {:ok, user} <- get_user(request),
       {:ok} <- update_db(user),
       {:ok} <- send_email(user) do
       return_http_message
  else
    {:error, reason} -> handle_error(reason)
    _ -> handle_ambigous_error
    # alternately, you could handle the errors
    # {:error, :update_db, details} -> handle_update_db_error(details)
    # {:error, :send_email, details} -> handle_send_email_error(details)
  end
end 

Why use with?

  • Chainable with multiple outputs
  opts = %{width: 10, height: 15}
  assert {:ok, 150} ==
    with {:ok, width} <- Map.fetch(opts, :width),
         {:ok, height} <- Map.fetch(opts, :height),
      do: {:ok, width * height}

Why use with?

  • Handle ugly case statement nesting
case File.read(path) do
  {:ok, binary} ->
    case :beam_lib.chunks(binary, :abstract_code) do
      {:ok, data} ->
        {:ok, wrap(data)}
      error ->
        error
    end
  error ->
    error
end
with {:ok, binary} <- File.read(path),
     {:ok, data} <- :beam_lib.chunks(binary, :abstract_code),
     do: {:ok, wrap(data)}

Can become:

With (or without with)

By ralucas

With (or without with)

  • 332