[eeps] EEP 49: Value-Based Error Handling Mechanisms

Michael Truog mjtruog@REDACTED
Wed Dec 5 02:49:04 CET 2018


I know this is a bit after most comments have occurred, but it seems ok 
since a decision hasn't been made yet.  I like the change to the 
begin/end, even though it attaches some magic interpretation of an ok 
2-tuple and an error 2-tuple.  Trying to make the begin/end change more 
complex to handle other return values for Success/Error conditions 
(perhaps by adding type specifications to the syntax) doesn't seem like 
a good direction, so the begin/end change seems good as-is.  The 
begin/end change in EEP 49 should also make the approach more efficient 
than the equivalent source code using functions, similar to what is 
described at 
https://github.com/erlang/eep/blob/master/eeps/eep-0049.md#obsoleting-messy-patterns 
because less data is required to accomplish the same control flow.

If the begin/end change in EEP 49 is not acceptable for some reason, or 
if people prefer an approach with functions, I have two functions which 
I have found useful to avoid "the pointy-case statement" problem that 
EEP 49 addresses.  They are eval/1 and accum/2, as shown below:

-type state() :: any().
-type eval_value() :: any().

-spec accum(L :: list({any(),
                        fun((any(), state()) ->
                            {ok, state()} | {{error, any()}, state()})}),
             State :: state()) ->
     {ok, state()} | {{error, any()}, state()}.

accum([], State) ->
     {ok, State};
accum([{Value, F} | L], State) ->
     case F(Value, State) of
         {ok, StateNew} ->
             accum(L, StateNew);
         {{error, _}, _} = Error ->
             Error
     end.

-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) ->
     case F(Value) of
         {ok, ValueNew} ->
             eval(L, [ValueNew | Output]);
         {error, _} = Error ->
             Error
     end.

These functions may be helpful in Erlang/OTP, in the absence of the EEP 
49 begin/end change.  With the EEP 49 begin/end change, they may help 
with backwards compatible source code but there doesn't seem like there 
should be any type-checking advantage (type-checking should be worse 
this way) or any advantage related to avoiding exceptions (i.e., 
avoiding the need to catch exceptions that causes the function call-path 
to be impure).  Both use a list of 2-tuples where the 2-tuple represents 
Value -> Function.  The eval Function is arity 1 and a success result is 
an ok tuple one larger than the size of the list (allowing a match on 
all results).  The accum Function is arity 2 to allow the accumulation 
of state data in the second argument, with the final state value getting 
returned in an ok 2-tuple with a success.

These functions could easily be added to the Erlang/OTP lists module to 
try and guide people away from creating pointy-case statement structures 
in their functions (causing the columns in the source code to grow to 
become less readable and possibly causing more functions to be created 
than necessary to break-up the functionality).  For handling functions 
that require more arguments, a tuple or list could be passed as the 
Value provided (similar to what is commonly done with init/1 functions 
used with OTP behaviours).

Some use of these functions can be seen at:
https://github.com/CloudI/CloudI/blob/ce54b4ae354b5633593fba6bf68f4eedfab7985c/src/lib/cloudi_core/src/cloudi_core_i_logger.erl#L813-L826
https://github.com/CloudI/CloudI/blob/develop/src/lib/cloudi_core/src/cloudi_core_i_configuration.erl#L3299-L3319

So, while I like the begin/end EEP 49 change, the accum/2 and eval/1 
functions described above may help the discussion further, as an 
alternative or additional change.

Best Regards,
Michael




More information about the eeps mailing list