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

_______________________________________________
eeps mailing list
[email protected]
http://erlang.org/mailman/listinfo/eeps

Reply via email to