[erlang-questions] Human readable errors in lists module

Mazen Harake <>
Wed Feb 11 16:18:47 CET 2009


Lets hope it stays like it is... Relieved to hear this.
thumbs up

/M

Kenneth Lundin wrote:
> Hi,
>
> As responsible for the Erlang/OTP team at Ericsson I can just say that
> we agree with Joe in this matter.
> We have no intention to add human readable errors to the list module
> or other library modules in standard lib.
>
> As a matter of fact I think a crash with function clause is quite
> clear because it informs you about
> that a named function did not have any clause which matched the
> arguments you called it with. And the arguments
> are also available in the crash info.
> As the source code and the documentation for lists in this case is
> available it should be possible to understand why the arguments was
> not accepted.
>
> A beginner might not have so easy to understand whats wrong but after
> just a few occurences of errors like that it should be
> quite obvious.
>
> /Kenneth Erlang/OTP Ericsson
>
> On Tue, Feb 10, 2009 at 10:46 PM, Joe Armstrong <> wrote:
>   
>> On Tue, Feb 10, 2009 at 4:22 PM, Adam Lindberg
>> <> 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
>>> 
>>> http://www.erlang.org/mailman/listinfo/erlang-questions
>>>
>>>       
>> _______________________________________________
>> erlang-questions mailing list
>> 
>> http://www.erlang.org/mailman/listinfo/erlang-questions
>>
>>     
> _______________________________________________
> erlang-questions mailing list
> 
> http://www.erlang.org/mailman/listinfo/erlang-questions
>   




More information about the erlang-questions mailing list