[erlang-questions] : Human readable errors in lists module

Raimo Niskanen raimo+erlang-questions@REDACTED
Wed Feb 11 16:52:36 CET 2009


On Wed, Feb 11, 2009 at 03:04:54PM +0000, Adam Lindberg wrote:
> 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.

Yes, but sometimes an interface error is hard to find at the interface.

> 
> 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?

It produces O(length(Ys)) overhead. The length is not
available without traversing.

In this case, for correct arguments, if done only at
the interface that is having an entry function with
a guard and traversing without guard, it would change
the current O(length(Ys) to K * O(length(Ys)). List
Ys first traversed by length(Ys), then Xs by length(Ks),
and then Ys and Xs by the loop producing the result.
Since length/1 is written in C I would estimate it would
to be less overhead than double, but measurable. And
the code will become two functions (uglier).

For the error case when length(Ys) =/= length(Xs) the
run time would be O(max(length(Ys), length(Xs)) instead
of O(min(length(Ys), length(Xs)) since both lists
always are traversed.

And if any of the lists is an improper list the symptom will
be a (confusing) function clause at the interface.

So, there are a number of small reasons Joe Armstrong
has his philosophy of writing small and beautiful code
and letting it crash.

> 
> 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
> > >
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://www.erlang.org/mailman/listinfo/erlang-questions

-- 

/ Raimo Niskanen, Erlang/OTP, Ericsson AB



More information about the erlang-questions mailing list