Is the with do: else: available in 1.3.0?

I have some use cases where with combined with an else block would really help
but it doesn’t compile.
I read here railway-development-in-elixir-using-with
that the else block becomes available in 1.3.0

Thanks,
Dragos

Yes, it is. Can you post a code sample and the error message you are getting?

Yes!

From the changes in 1.3.0 on github:

[Kernel] Support else chunks in with

It’s easy to sometimes get the syntax not quite right with them, and it can be difficult to troubleshoot. I use with all of the time now and I made a gist on many of the syntax variations, and I’ve finally come to the following syntax variation that I like:

with(
    # You can comment each line with the paren syntax
    {:ok, bar} <- foo(),
    {:ok, result} <- baz(bar)
) do
    {:ok, result}
else
    # To show that you can match on the error returned
    {:error, reason} -> {:error, reason}
    error -> {:error, inspect error}
end

There are a couple of points to remember:

  1. The with block proper requires the arrows to point to the left. (This still trips me up sometimes).
  2. There is no , after the last with block clause. (This also gets me if I’m copy/pasting and won’t compile)
  3. In the else block, we’re matching on the error, so the arrows go to the right.

I prefer this paren-style version because it makes things clearer to me on where I need the commas, where I need left arrows, and it separates visually the happy path return do statement more clearly to me.

2 Likes

The code that doesn’t compile, looks like this:

with {_, responder, _, _} <- get_child(state.supervisor, :responder),
     {_, interface, _, _} <- get_child(state.supervisor, :interface),
     do: Responder.Supervisor.respond(responder, {reaction, interface}),
   else: IO.puts "not ok"

and the compilation error:

== Compilation error on file lib/dobar/robot.ex ==
** (FunctionClauseError) no function clause matching in :elixir_with.expand_else/2

(elixir) src/elixir_with.erl:58: :elixir_with.expand_else({{:., [line: 53], [{:aliases, [counter: 0, line: 53], [:IO]}, :puts]}, [line: 53], [“not ok”]}, %Macro.Env{aliases: [{Interface, Dobar.Interface}, {Conversation, Dobar.Conversation}, {Responder, Dobar.Responder}, {Intent, Dobar.Intent}, {Reaction, Dobar.Reaction}], context: nil, context_modules: [Dobar.Robot], export_vars: nil, file: “/Users/dragos/Playground/Elixir/dobar/lib/dobar/robot.ex”, function: {:handle_info, 2}, functions: [{Kernel, [!=: 2, !==: 2, *: 2, +: 1, +: 2, ++: 2, -: 1, -: 2, --: 2, /: 2, <: 2, <=: 2, ==: 2, ===: 2, =~: 2, >: 2, >=: 2, abs: 1, apply: 2, apply: 3, binary_part: 3, bit_size: 1, byte_size: 1, div: 2, elem: 2, exit: 1, function_exported?: 3, get_and_update_in: 3, get_in: 2, hd: 1, inspect: 1, inspect: 2, is_atom: 1, is_binary: 1, is_bitstring: 1, is_boolean: 1, is_float: 1, is_function: 1, is_function: 2, is_integer: 1, is_list: 1, is_map: 1, …]}], lexical_tracker: #PID<0.144.0>, line: 45, macro_aliases: [], macros: [{Kernel, [!: 1, &&: 2, …: 2, <>: 2, @: 1, alias!: 1, and: 2, binding: 0, binding: 1, def: 1, def: 2, defdelegate: 2, defexception: 1, defimpl: 2, defimpl: 3, defmacro: 1, defmacro: 2, defmacrop: 1, defmacrop: 2, defmodule: 2, defoverridable: 1, defp: 1, defp: 2, defprotocol: 2, defstruct: 1, destructure: 2, get_and_update_in: 2, if: 2, in: 2, is_nil: 1, match?: 2, or: 2, pop_in: 1, put_in: 2, raise: 1, raise: 2, reraise: 2, reraise: 3, …]}], module: Dobar.Robot, requires: [GenServer, Kernel, Kernel.Typespec], vars: [reaction: nil, state: nil]})
(elixir) src/elixir_with.erl:48: :elixir_with.expand/3
lib/dobar/robot.ex:45: (module)

thanks for the reply - yes, i know about the <- when matching inside with and everything works
until i try to add an else block.
Now that i saw the syntax you’re using, i think i’m gonna give it a try.

Thanks,
Dragos

That isn’t how else works. else works like else in try where you have patterns on the LHS IE. I’m not entirely sure if it’s possible to do else: with that syntax actually.

What version of elixir are you on specifically?

This code here produces a much nicer error for me:

    with :foo <- :foo,
      :bar <- :bar,
      do: :baz,
    else: IO.puts "yo"
** (CompileError) mix.exs:7: expected -> clauses for else in with
    (elixir) src/elixir_with.erl:48: :elixir_with.expand/3
    mix.exs:6: (module)

$ elixir --version
Erlang/OTP 19 [erts-8.0.2] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.3.0

Yes, I agree with @benwilson512. I think if you change it to the “fuller” syntax, include a match in the else block you should be good.

EDIT: Don’t forget to remove the comma after the last clause in the with block before do.

Also, the way you have it, if the call to Responder.Supervisor.respond(responder, {reaction, interface}) fails with an error, this will be handled outside of the function. I mean, this may be what you want, but perhaps we could get some comments from others on the various dynamics of this.

I personally have only seen bare return tuples in the do block, as opposed to another function call. This way, all error handling happens within your nice with statement cleanly.

yes, i can actually return a {:ok, something} when matching the result of the get_child.
Regarding the call to the Responder, this won’t error and it if happens, i’m not interested in
the error, at least not inside the with block.

Thanks for the replies,
Dragos

1 Like