How to handle nested case

19 views
Skip to first unread message

Gian Lorenzo Meocci

unread,
Sep 18, 2021, 4:50:18 PM9/18/21
to erlang-q...@erlang.org
Hi,
I have a function like this:

get_nas_from_conf() ->
case get_radius_host() of
{ok, Server} ->
case get_radius_port() of
{ok, Port} ->
case get_radius_secret() of
{ok, Secret} ->
{Server, Port, Secret};
E -> E
end;
E ->
E
end;
E ->
E
end.

Which is the best way to write this kind of function?

I'd like to have a with operator like in Elixir, to rewrite my function in this way:

get_nas_from_conf() ->
with {ok, Server} <- get_radius_host(),
{ok, Port} = get_radius_port(),
{ok, Secret} = get_radius_secret() ->
{Server, Port, Secret};
catch
{error, _Reason} = E ->
E
end.

Any suggestion?

Michael Truog

unread,
Sep 18, 2021, 5:23:52 PM9/18/21
to Gian Lorenzo Meocci, erlang-q...@erlang.org
I have used (from https://github.com/CloudI/CloudI/blob/a43715b71facafc9a8ad7c7fe55c7383773c22df/src/lib/cloudi_core/src/cloudi_core_i_configuration.erl#L6022-L6039):
-type eval_value() :: number() | atom() | list(). -spec eval(L :: list({eval_value(), fun((eval_value()) -> {ok, any()} | {error, any()})})) -> tuple(). eval(L) -> eval(L, []). eval([], Output) -> erlang:list_to_tuple([ok | lists:reverse(Output)]); eval([{Value, F} | L], Output) when is_number(Value) orelse is_atom(Value) orelse is_list(Value) -> case F(Value) of {ok, ValueNew} -> eval(L, [ValueNew | Output]); {error, _} = Error -> Error end.

However, the eval function above is assuming each function in the 2-tuple list is taking a value it is paired with as input.  The return tuple has the 'ok' atom to show it is a success case.  For your situation, you wouldn't need an input value for your function values (so only a list of function values would be necessary).

Best Regards,
Michael

Attila Rajmund Nohl

unread,
Sep 19, 2021, 3:07:01 AM9/19/21
to Gian Lorenzo Meocci, Erlang
Hello!

One way (I haven't actually tried to compile this, but hopefully you
get the idea):

get_nas_from_conf() ->
try
{ok, Server} = get_radius_host(),
{ok, Port} = get_radius_port(),
{ok, Secret} = get_radius_secret(),
{Server, Port, Secret};
catch
error:{badmatch, E} ->
E
end.

Of course, if the badmatch comes from the get_* functions, this
construct might catch that too, but you might be able to ensure this
doesn't happen.

Gian Lorenzo Meocci <glme...@gmail.com> ezt írta (időpont: 2021.
szept. 18., Szo, 22:50):

Stanislav Ledenev

unread,
Sep 19, 2021, 5:22:05 AM9/19/21
to Gian Lorenzo Meocci, erlang-questions
Rewrite your get_* functions to throw and do smth. like this:

get_nas_from_conf() ->
    try
        {ok, {get_radius_host(), get_radius_port(), get_radius_secret()}}
    catch
        error:E ->
            {error, E}
    end.

And don't worry, throw is the simple goto in Erlang.


сб, 18 сент. 2021 г. в 23:50, Gian Lorenzo Meocci <glme...@gmail.com>:

Attila Rajmund Nohl

unread,
Sep 19, 2021, 9:23:14 AM9/19/21
to Michael Malter, Erlang
Hello!

The functions *are* executed conditionally - if get_radius_host()
doesn't return {ok, Server}, then get_radius_port() won't be called.

Michael Malter <airl...@gmail.com> ezt írta (időpont: 2021. szept.
19., V, 10:08):
>
> Hi
>
> Now it's kinda ugly.
> You could just keep the lines from your try clauses and put the result in a case or a function.
>
> Anyway, the execution flow is different now. I think OP wanted the get functions executed conditionaly.
>
> I will repeat, just put the cases in functions.
>
> Michaël Malter

Gian Lorenzo Meocci

unread,
Sep 19, 2021, 9:37:07 AM9/19/21
to Attila Rajmund Nohl, Erlang
I think that there isn't a generic solution for this problem. It’s time to introduce the 'with' operator in Erlang 😂

Paulo F. Oliveira

unread,
Sep 19, 2021, 10:40:27 AM9/19/21
to Gian Lorenzo Meocci, Erlang
Hi, Gian.

You might be interested in reading [1], first shown to the Erlang world in [2] (there's more info. in the archives, if you search).

FYI, it is being hacked away, at this very moment, in SpawnFest 2021: https://github.com/spawnfest/eep49ers.

Cheers.

- Paulo F. Oliveira

Gian Lorenzo Meocci

unread,
Sep 19, 2021, 11:14:11 AM9/19/21
to Paulo F. Oliveira, Erlang
Thanks a lot for the link!
--

Gian Lorenzo Meocci

unread,
Sep 19, 2021, 11:35:50 AM9/19/21
to Paulo F. Oliveira, Erlang
the proposal and the implementation are pieces of art!
Thanks for your work.
--

hokan.s...@bredband.net

unread,
Sep 19, 2021, 2:36:16 PM9/19/21
to erlang-q...@erlang.org

Another option is to simply turn it into a sequential series of function calls.

This avoids the nesting though it's more verbose than the try or with variants.

get_nas_from_conf() ->
case get_radius_host() of
{ok, Server} -> step2(Server)
E -> E
end.
 
step2(Server) ->
case get_radius_port() of
{ok, Port} -> step3(Server, Port)
E -> E
end.
 
step3(Server, Port) ->
case get_radius_secret() of
{ok, Secret} -> {Server, Port, Secret};
E -> E
end.

Igor Clark

unread,
Sep 19, 2021, 6:56:41 PM9/19/21
to Gian Lorenzo Meocci, Erlang
Just read it all through and totally agree, this is really very elegant. Lovely to see. If all goes well, is there a rough guideline for how long it would take for this extension to appear in mainline releases? (If that’s the plan!)

Thanks!

On 19 Sep 2021, at 16:35, Gian Lorenzo Meocci <glme...@gmail.com> wrote:



Richard O'Keefe

unread,
Sep 19, 2021, 8:32:42 PM9/19/21
to Gian Lorenzo Meocci, Erlang Questions
What is wrong with 'try'?

ok({ok,R}) -> R;
ok(E) -> throw(E).

try
Server = ok(get_radius_host()),
Port = ok(get_radius_port()),
Secret = ok(get_radius_secret),
{ok,{Server,Port,Secret}}
catch throw:E ->
E
end

WARNING: UNTESTED CODE.

I'd be inclined to do something slightly different:

ok({ok,R}, F) -> F(R);
ok(E) -> E.

ok(get_radius_server(), fun (Server) ->
ok(get_radius_host(), fun (Port) ->
ok(get_radius_secret(), fun (Secret) ->
{ok,{Server,Port,Secret}}
end) end) end)

(which is rather like using >>= \x -> rather than 'do' in Haskell).

I would also be wondering whether it is possible for a non-OK result to be
ambiguous. Are there values for E that could have come from more than
one of these 'get_*' functions? Might the caller of your code care about
where the error came from?

I find myself wondering whether a simple
{ok,Server} = ...
{ok,Port} = ....
{ok,Secret} = ...
might not be the most idiomatic "let it crash" Erlang way of all.

There is some important context missing here.
* Why isn't "let it crash" the right answer here?
* Why should the caller be told that an error occurred but
in a possibly ambiguous way?
* Is there no other validation to be done?
* What do the surrounding comments say?

Michael Truog

unread,
Sep 19, 2021, 10:05:48 PM9/19/21
to Richard O'Keefe, Gian Lorenzo Meocci, Erlang Questions
If (try) catch is used that means the source code will not be
referentially transparent (it will be impure) because any of the three
functions may also throw.  That makes the approach an anti-pattern,
something to avoid to promote code that easy to inline, optimize, test
and understand.

Richard O'Keefe

unread,
Sep 19, 2021, 10:23:21 PM9/19/21
to Michael Truog, Gian Lorenzo Meocci, Erlang Questions
It seems extremely likely that the code is not referentially
transparent to begin with.
That was meant as an *example* of try/catch, and the question of ambiguity was
addressed in that message of mine and in someone else's.

My *actual* recommendation is the higher-order combinator ok(f(), fun
(R) -> ... end), which I like precisely because it is
straightforwardly functional and adaptable.

Michael Truog

unread,
Sep 19, 2021, 11:02:04 PM9/19/21
to Richard O'Keefe, Gian Lorenzo Meocci, Erlang Questions
On 9/19/21 7:22 PM, Richard O'Keefe wrote:
> It seems extremely likely that the code is not referentially
> transparent to begin with.
> That was meant as an *example* of try/catch, and the question of ambiguity was
> addressed in that message of mine and in someone else's.
>
> My *actual* recommendation is the higher-order combinator ok(f(), fun
> (R) -> ... end), which I like precisely because it is
> straightforwardly functional and adaptable.

Your approach with continuation-passing style anonymous functions and
explicit matches are good if "let it crash" is possible. However, often
error information needs to be returned as a value (e.g., an error with
input that is not an Erlang process failure). My view of the problem was
input validation while the example source code looks like it is related
to the validity of the static configuration provided.  If the errors in
the example are static system configuration, then it is best to have the
fail-fast approach in your examples.

Best Regards,
Michael

Andreas Schultz

unread,
Sep 20, 2021, 3:55:18 AM9/20/21
to Gian Lorenzo Meocci, erlang-questions
Hi,

With the help of https://github.com/eryx67/erlando.git, you can write this as:

-compile({parse_transform, do}).
-compile({parse_transform, cut}).

get_nas_from_conf() ->
    do([error_m ||
           Server <- get_radius_host(),
           Port <- get_radius_port(),
           Secret <- get_radius_secret(),
           return({Server, Port, Secret})
       ]).

This will return {ok, {Server, Port, Secret}} or {error, Error}.

Regards,
Andreas
--

Andreas Schultz

Jesper Louis Andersen

unread,
Sep 20, 2021, 5:05:25 AM9/20/21
to Gian Lorenzo Meocci, Erlang (E-mail)
What about something like:

get_nas_from_conf() ->
  {ok, Server} = get_radius_host(),
  {ok, Port} = get_radius_port(),
  {ok, Secret} = get_radius_secret(),
  {Server, Port, Secret}.

Errors are now just crashes. But if the errors are really truly something you want to handle, chances are you need some other flow anyway.
--
J.

Fred Hebert

unread,
Sep 20, 2021, 9:25:07 AM9/20/21
to Jesper Louis Andersen, Gian Lorenzo Meocci, Erlang (E-mail)
This is timely, because we're trying to land EEP-49: https://github.com/erlang/otp/pull/5216

Which would turn the original code into:

get_nas_from_conf() ->
  begin
    {ok, Server} <- get_radius_host(),
    {ok, Port} <- get_radius_port(),
    {ok, Secret} <- get_radius_secret(),
    {Server, Port, Secret}
  cond
    {error, _} = E -> E
  end.

The 'cond' keyword is still in the air (the EEP specified 'else' but we hit an implementation snag).

Gian Lorenzo Meocci

unread,
Sep 20, 2021, 9:33:29 AM9/20/21
to Fred Hebert, Erlang (E-mail)
Hi Fred,
this is a good news because it's generic and rooted in the language!

Do you have any timing estimation to have it in the main branch?
Thanks a lot
--

Michael P.

unread,
Nov 17, 2021, 8:32:03 AM11/17/21
to erlang-q...@erlang.org
:-)

Delicious pieces of conversation.

:-)

~Michael


--

Programs that compile are compilers.
Programs that do not compile are
either no compilers, buggy compilers,
or compilers that cannot be compiled.





Reply all
Reply to author
Forward
0 new messages