Question on functional style

Håkan Stenholm <>
Fri Sep 9 20:44:14 CEST 2005


Vance Shipley wrote:

>Gurus,
>
>Is it bad form to use try/catch to handle non-local returns
>other than exceptions?
>
>I have some code which does a battery of tests in several phases.
>The tests are simple so it makes sense to me to just perform 
>them sequentially in one function and throw when I want to skip
>to the next phase:
>
>start(Arg) ->
>    try begin
>        case lists:member(foo, Arg) of
>            true ->
>                throw(done);
>            _ ->
>                ok
>        end,
>        case lists:member(bar, Arg) of
>            true ->
>                throw(done);
>            _ ->
>                ok
>        end,
>        case lists:member(baz, Arg) of
>            true ->
>                throw(done);
>            _ ->
>                ok
>        end
>    end
>    catch
>        throw:done ->
>            phase2()
>    end.
>
>phase2() ->
>    done.
>  
>

The following replacement code springs to mind:

start(Arg) ->
    F = fun(E) -> 
        lists:member(E, Arg) 
    end,
    Matches = [foo,bar,baz],
    case lists:any(F, Matches) of
        true -> phase2();
        false -> ok
    end.

phase2() ->
    done.


This does limited match testing with the help of lists:any/2,  R10B-6 
uses the following code:

any(Pred, [Hd|Tail]) ->
    case Pred(Hd) of
    true -> true;
    false -> any(Pred, Tail)
    end;
any(_, []) -> false.  


which stops after the first match, without resorting to try, catch or 
throw usage. Another choice is to do the following:

start(Arg) ->
    try begin
        false = lists:member(foo, Arg),
        false = lists:member(bar, Arg),
        false = lists:member(baz, Arg)
    end
    catch
        throw:error ->    % should it be 'exit' ?
            phase2()
    end.

phase2() ->
    done.

This relies on Erlang generating a runtime exception if one of the 
lists:member/2 calls become = 'true'. My personal choice would be to 
write the code without any exceptions, as their use implies a unexpected 
execution path in the code, rather than a standard path.



>
>While this is easiest for me to understand my own code I wonder
>whether it is good functional style.  I do end up with a very
>large function and the throw is not really an "exception".
>
>Alternatively I can hard cpode the sequence into the test functions:
>
>start(Arg) ->
>    f1(Arg).
>
>f1(Arg) ->
>    case lists:member(foo, Arg) of
>        true ->
>            phase2();
>        _ ->
>            f2(Arg)
>    end.
>
>f2(Arg) ->
>    case lists:member(bar, Arg) of
>        true ->
>            phase2();
>        _ ->
>            f3(Arg)
>    end.
>
>f3(Arg) ->
>    case lists:member(baz, Arg) of
>        true ->
>            phase2();
>        _ ->
>            ok
>    end.
>
>phase2() ->
>    done.
>
>
>I find this makes it harder to follow the sequence and the tests
>can't be reused in a different order.  I could pass the next function 
>as an argument I suppose ...
>
>It seems to me that the above is less deterministic.  The return 
>from f1/1 depends on what happens in f2,f3,...fn.
>
>Using the first form I can actually perform many of the tests using
>an if statement and pattern matching.
>
>Your opinions are welcome.
>
>	-Vance (who has never taken a CS course)
>
>
>  
>




More information about the erlang-questions mailing list