Quantum of Elixir or why in rescue it just match the name of the error?

I want define a macro like this.

defmodule Demo do
  defmacro maybe(do: block) do
    quote do
      try do
        unquote(block)
      rescue
        e in [MatchError] -> e.term
        # %MatchError{term: term} -> term
        e -> reraise e, __STACKTRACE__
      end
    end
  end
end

Why in the rescue it just match the name of the error?
In another words, why can not use the code like %MatchError{term: term} -> term in here?
About 5 years ago there is a question about this.
The different is that I know what is right syntax, but I do not know why it must like that way.

One reason may be the internal structure of the exception struct can change over time

1 Like

Let me patch more background:

I try to use maybe replace with, like this

{:error, b } = maybe do
  {:ok, a} ={:ok, 1}
  {:ok, b } = {:error, a+1}
  #code_with_b...
end

For the maybe define, I try use the catch to make the code more cleaner, like this:

try do
   unquote(block)
catch
  :error, {:badmatch, term} -> term
end

But I do not like this one, because for me, MatchError is more meaningfull than :badmatch.

If the error is not the erlang error, we can use a trick like:

catch
   :error, %TheErrorName{field: v} -> v

But for MatchError, the trick is not work.

José Valim happened to talk about it in Reviewing Elixir - Part 4 of the Thinking Elixir podcast.

Briefly summarized, the reason is indeed to make it very hard for people to depend in their rescue clauses on the internals of the exception struct, as this might change over time. (Side note: debugging exceptions being raised because of other exceptions not being recognized properly is very much not fun.)
You can still do it inside the rescue clause if you really really want to, but at that point you’ll have thought really hard about if this really is the approach you want to take and hopefully have reconsidered.

4 Likes

Thank you for your answer.

For this question wheather Erlang can change the error message?
If the message can also be changed by erlang how to do this work in elixir?

Make a new operationer like ?= in erlang?

Now I have some more interesting things about rescue and catch.

%MatchError{} = try do
    raise MatchError, term: {:ok, 1}
  catch
    :error, %MatchError{} = v -> v
end
%MatchError{} = try do
    raise MatchError, term: {:ok, 1}
rescue
     v -> v
end

If the error is raised by ourself, we can catch it and rescue from it. That is good.

But when runtime raise it, like this:

try do
  :ok = :error
catch
  # not work
  # :error, %MatchError{}=v -> v 
  :error, v -> v # now v is not a Error, but a tuple
end

%MatchError{} = try do
  :ok = :error
rescue
  v -> v # now v is a MatchError
end

We can still rescue from the error, but when we try to catch it, we get a different thing(a tuple).
That is like Schrodinger’s cat.

Maybe we can call this quantum of Elixir! :blush:

Check out the second example from the documentation for Kernel.try and associated machinery:

try do
  :erlang.error(:badarg)
catch
  :error, :badarg -> :ok
end
#=> :ok

A new mechanism for this was introduced in OTP 24 (c.f. EEP 54). The ‘extra information’ is kept separate from the main value that is being raised, to make sure that existing code did not break.

It was necessary to do it this way in part because the whole error mechanism in Erlang predates maps and was thus solely built on top of atoms and tuples.

2 Likes