[erlang-questions] Human readable errors in lists module

Adam Lindberg adam@REDACTED
Wed Feb 11 16:04:54 CET 2009


I agree with most arguments here. I think the only thing that bothers me is that the function returns an internal error, not an interface error.

As a side question, would adding a guard "when length(Ys) == length(Xs)" produce any overhead? That is, does length take time to compute or is it store in memory?

Cheers,
Adam

----- "Kenneth Lundin" <kenneth.lundin@REDACTED> 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 <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