EEP 49 (Value-Based Error Handling Mechanisms)

I have previously written about EEP 49.

We had a discussion in the OTP team about what the matching operator should be. In the previous OTP Technical Board meeting, we decided that we should not overload the <- operator used in list comprehensions. Eliminating operators that could be useful for other purposes in the future, we ended up with <~ earlier this week. Unfortunately, <- and <~ look quite similar to each other (especially in some fonts and text sizes), so we wasn’t completely happy with that choice.

After some more discussion and thinking, we came up with ?=. Now that may seem to cause ambiguity since macro invocations start with ?, but it is not. Even if there exists a macro named =, it must be invoked like ?'='.

Thus, the previous example will now look like:

commit_write(OpaqueData) ->
    maybe
        ok ?= disk_log:sync(OpaqueData#backup.file_desc),
        ok ?= disk_log:close(OpaqueData#backup.file_desc),
        ok ?= file:rename(OpaqueData#backup.tmp_file, OpaqueData#backup.file),
        {ok, OpaqueData#backup.file}
    end.

Here is the pull request for the updated implementation.

We will have an OTP Technical Board meeting soon to discuss this latest revision of the EEP and approve it (I hope). We will update the EEP after the meeting.

9 Likes

I hadn’t seen that EEP before, it looks great! I usually end up implementing my own short-circuting higher-level sequencing function in every other project I do, and this will definitely be a cleaner way.

?= exists as an operator in groovy, as the “Elvis assignment operator”, and has a similar(ish) purpose, general handling of “unhappy path” values (or setting of default values). It assigns the value on the right hand side to the variable on the left hand side, if and only if the left hand side is falsy (null, empty string, etc). errorMessage ?= "Unknown error"

Maybe not the most popular language, but still the familiarity and similarity in function is an added bonus to those familiar with it!

3 Likes

Whilst I don’t have the full context of the EEP and its history, I prefer this operator as the “?” part of it seems a natural fit with the keyword “maybe”.

7 Likes

I’ve seen that EEP and it looks awesome! I do quite like ?= after looking at it for a couple of minutes, it aligns better with what = is doing but with a possibly conditional early-out.

3 Likes

I recall Joe saying to me that if you add something to a language, you should also take something out (roughly paraphrased). So what should be removed to make space for this? :innocent:

5 Likes

Tuple calls were taken out a while ago? I miss the higher typed module support it gave, lol.

5 Likes

I suggest the and and or operators. And also size/1. :smile:

Why we will not remove them:

5 Likes

Parameterized modules was an experimental feature that we had in the language for several releases. The feature mostly worked but was not fully supported by the rest of the OTP (I don’t remember exactly, but it could have been release handling). Eventually, we decided that we would either have to fully support parameterized modules or take them out. We took them out.

Tuple calls (needed to implement parameterized modules) remained for a while until we removed them too.

I can think of one supported feature of the language that we have actually managed to remove. It is another kind of tuple call that was frequently used before funs were invented:

MF = {some_module, some_function},
apply(MF, Arguments)
3 Likes

Thanks for the historical context. Can I ask what the issue with size/1 is? Is it that pattern matching is a more idiomatic solution?

EDIT: never mind, found it amongst Erlang -- Common Caveats

4 Likes

Maybe it is too late for this comment, but the reason why reusing <- makes sense to me is because both maybe and comprehensions are monadic operations where <- have the same semantic meaning. I understand that Erlang does not have monads, and most users won’t be aware of such background, but I wanted to mention there is a framework to describe it. :slight_smile:

Barring that, IMO ?= is also a solid choice. :+1:

10 Likes

I’m in fully agreement with Jose on this one and also hopefully not too late to weigh in… I’d favor <- unless it significantly complicates the underlying implementation. I don’t think anyone will be confused.

6 Likes

I was less a fan of parameterized modules, but tuple calls allows for a lot of usually higher-typed functionality (like think OCaml’s first class modules), which is very difficult to emulate otherwise (have to go back down to carrying a load of data everywhere you go).

2 Likes

Thanks for all comments in this thread.

We have now had the OTP Technical Board meeting. While we spent some time discussing monads :smile:, we still decided to go with the ?= operator, since there is not really any cost to adding a new operator and overloading an existing operator will not make it any easier to document it, or for a new user to understand it.

I will update the EEP, and I will continue to implement the maybe construct in the rest of OTP (shell, tools, documentation, and so on).

7 Likes

?= can have a fun case with macros where you do something like:

-define(OP, =).

f(X) ->
    maybe
        X ??OP exp()
    end

There’s no real practical case for it, but in doing so the X ?= exp() expansion never takes place and instead we get X "=" exp() as a result. That’s an edge case and not something I would expect to seriously be an issue.

5 Likes

I refer you to my various rants on the evil of macros :wink:

3 Likes

I much prefer ?= as I already use in in LFE in cond as a match test. :wink: :smile:

6 Likes

When I try to compile your example, I get the following error message:

t.erl:8:12: illegal macro call '?'?''
%    8|         X ??OP exp()
%     |            ^

(With both OTP 24 and my EEP-49 branch.)

?? is a macro operator, but only valid inside the definition of a macro like this:

-define(STRINGIZE(Arg), ??Arg).

Defined like this, the ?STRINGIZE() macro will turn its argument into a string.

Even if ?? did not have a special meaning, it would still not work, because the preprocessor never combines adjacent tokens into a new token. That is, the following example:

-define(EQ, =).
-define(LT, <).

t(X, Y) ->
    X ?EQ?LT Y.

will be expanded to:

t(X, Y) ->
    X = < Y.

which is a syntax error:

t.erl:8:11: syntax error before: '<'
%    8|     X ?EQ?LT Y.
%     |           ^
4 Likes

Ah nice. I absolutely didn’t remember that ??MACRO only worked in other macros.

No concerns then.

4 Likes

EEP 49 - Value-Based Error Handling Mechanisms has now been updated with ?= as the conditional matching operator and is now marked as approved. Thanks to @MononcQc for coming up with the idea and writing the EEP.

I have now created a new pull request with the complete implementation of EEP 49. Thanks to @MononcQc and @peerst for creating their prototype implementation, which saved some time for me.

10 Likes

This is very exciting! Thank you all. I’m looking forward to using this both in my Erlang programs and in code generated by the Gleam compiler

4 Likes