[erlang-questions] Human readable errors in lists module
Joe Armstrong
erlang@REDACTED
Tue Feb 10 22:46:15 CET 2009
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
>
More information about the erlang-questions
mailing list