[erlang-questions] Human readable errors in lists module

Kunthar kunthar@REDACTED
Tue Feb 10 23:02:23 CET 2009


I love "let it crash" way :)

Simplicity is the ultimate sophistication!
Leonardo Da Vinci, da great Italian magician

Peace
Kunth


On Tue, Feb 10, 2009 at 11:46 PM, Joe Armstrong <erlang@REDACTED> wrote:
> On Tue, Feb 10, 2009 at 4:22 PM, Adam Lindberg
> <adam@REDACTED> wrote:
>> Hi,
>>
>> Is there any reason that for example lists:zipwith/3 returns a function clause instead of a human readable error when the lists are of different length?
>
> Yes
>
> Everything has a cost - if we applied this principle rigidly to all
> functions in
> the system then every function in the entire system would be extended with
> additional code. This make the code more difficult to read, and makes the
> program larger (think cache hits).
>
> Suppose we have a function that maps a to 1 and b to 2, I'd write it like this:
>
> f(a) -> 1;
> f(b) -> 2.
>
> should I then add an additional clause to warn for bad arguments?
>
> f(a) -> 1;
> f(b) -> 2;
> f(_) -> error('arg is not a or b').
>
> This adds nothing to the clarity of the code since the original expresses exact
> the intention of the program *and nothing else*
>
> Programs that are cluttered with additional error messages are difficult to read
> (which increases the chances of an error) an less efficient
> (everything has a cost)
>
>> It might seem obvious at first but the reason I'm asking is because a colleague of >mine just spent a long time debugging code with used list:zipwith/3 and it threw >this error. What he did at first was to check that all arguments to lists:zipwith/3
>> > was not zero (this is what the function clause error indicated).
>
> *everybody* spends a long time figuring out what when wrong the first time
> they get an error of a particular type - then they learn - when you've seen
> these errors a few times you'll find that you can find the error very quickly
>
> The fact that erlang crashes at the first error and prints something really
> aids debugging ...
>>
>> lists:zipwith/3 could have been implemented as below (or something similar):
>>
>> zipwith(F, [X | Xs], [Y | Ys]) -> [F(X, Y) | zipwith(F, Xs, Ys)];
>> zipwith(F, [], []) when is_function(F, 2) -> [].
>> zipwith(F, [], Ys) -> error(lists_of_different_length). %% Just a proposal, insert
>> zipwith(F, Xs, []) -> error(lists_of_different_length). %% preferred error mechanism here.
>>
>> The function clause, noting the arguments as [#Fun..., [], [5,6,7,...]], is kind of misleading since it happens inside the lists:zipwith/3 function.
>>
>> I can see the purists' argument here "that it is really a function clause" but I also see the pragmatist argument "that it is much easier to debug."
>
> Nw let's look at the error message - here's an experiment
>
> 1> lists:zipwith(fun(X,Y) -> X + Y end, [1,2,3],[4,5]).
> ** exception error: no function clause matching
>                    lists:zipwith(#Fun<erl_eval.12.113037538>,[3],[])
>     in function  lists:zipwith/3
>
> To the experienced eye the error is clear
>
> zipwith(Fun, [3], []) doesn't match any of the clauses defining zipwith
>
> Show me the code Luke ... (just run less on
> /usr/local/lib/erlang/stdlib ... ish)
>
> zipwith(F, [X | Xs], [Y | Ys]) -> [F(X, Y) | zipwith(F, Xs, Ys)];
> zipwith(F, [], []) when is_function(F, 2) -> [].
>
> This is two lines of code.
>
> So zipwith(Fun, [3], []) doesn't match one of these two lines of code ...
>
> this is actually *shorter* than the documentation (a lot shorter)
>
> what does the documentation say?
>
>  zipwith(Combine, List1, List2) -> List3
>       Types  Combine = fun(X, Y) -> T
>       List1 = [X]
>       List2 = [Y]
>       List3 = [T]
>       X = Y = T = term()
>      Combine the elements of two lists of equal length into one list.
>                                                                 **************
> What I have noticed teaching Erlang is that beginners make almost exactly
> the same mistakes as experienced users - the difference is in the time
> it takes them to debug an error.
>
> The first time is *always* slow - then you learn.
>
> (this is universally true - while I can fix erlang programs pretty quickly
> I can stare at simple javascript errors for ages before twigging what
> went wrong)
>
> But there's a more subtle problem.
>
> Let's try your suggestion. (I put your zipwith in a module test4)
>
> try4:zipwith(fun(X,Y) -> X + Y end, [1,2,3],[4,5]).
> ** exception error: lists_of_different_length
>     in function  try4:zipwith/3
>     in call from try4:zipwith/3
>
> It works - great - we think ... but what about this?
>
> try4:zipwith(fun(X,Y) -> X + Y end, {1,2},[4,5]).
> ** exception error: no function clause matching
>                    try4:zipwith(#Fun<erl_eval.12.113037538>,{1,2},[4,5])
>
> Now what? Opps the guards were wrong - need to add a few
> when is_list(..) guards. Or do we want an error message that says
> error,arg1 should not be a tuple ....
>
> There are a very large number of ways we can supply incorrect arguments
> and we can't program all of them.
>
> So what do we do - we only write patterns that match the desired cases
> *and nothing else* - this is part of the erlang "let it crash" philosophy.
>
> In erlang we don't do defensive programming - (or rather we do do it using
> patterns)
>
> Best
>
> /Joe Armstrong
>
>
>> Cheers,
>> Adam
>> _______________________________________________
>> erlang-questions mailing list
>> erlang-questions@REDACTED
>> http://www.erlang.org/mailman/listinfo/erlang-questions
>>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://www.erlang.org/mailman/listinfo/erlang-questions
>



More information about the erlang-questions mailing list