[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