[erlang-questions] Package Support/Use

Richard A. O'Keefe ok@REDACTED
Wed Nov 8 04:29:03 CET 2006


Mats Cronqvist <mats.cronqvist@REDACTED> wrote:
	richard o'keefee (rok) feel that returning tagged tuples is better.

NO I DO NOT.  This misses the whole point of everything I have said,
which is that *SOMETIMES* tagged tuples are better and *SOMETIMES*
exceptions are better.

	"just define
		ok({ok,X}) -> X;
		ok(Error) -> signal some kind of error."
	
	   he then goes on to argue that RokErl is just as good as
	BettErl. that's not actually true (since you only get the term that
	didn't match, and no info where it came from),

But if you call exit(Term) or throw(Term), you only get the term that
was thrown, and no info on where it came from.

	and in any case irrelevant to my point; that TradErl (i.e. the
	bulk of existing code) is needlessly verbose and gives poor error info.

I thought that "TradErl" meant the "tagged tuples" interface style.
It now appears that "TradErl" means something different, namely the
way that "the bulk of existing code" (ab)uses that interface style.
It is important to distinguish those two things.  I *am* defending
the tagged tuples interface style as appropriate in many many cases.
I am *not* defending "the bulk of existing code".

As for "where it came from", I expect an error term to tell me WHAT THE
PROBLEM IS.  The only time I ever want it to tell me where it came from
is when the problem description is *wrong* and I need to fix the code
that creates the problem description.  When exceptions were added to
Quintus Prolog, it was often the case that an exception raised in one
place needed to be reported *as if* it had been raised in another, for
the simple reason that the place where it was actually raised was deep
inside system code that the user couldn't get at in any way.

	   OTOH, that's a bit like asking "hey richard, have you given up smoking
	crack yet?", isn't it? please answer yes or no.
	
Not in the least.  If the advocate of a particular style uses it in a poor
way, that really is evidence that the style may not be as good as he thinks.
In my dialect, crack is OED sense 5 "Brisk talk, conversation".

	   rok says; "The real issue is how exceptional the exceptional
	condition is and whether it is likely that the caller can do anything
	in an error case other than pass it on."
	
	   i think the real issue is if one regards "exceptions" as truly
	exceptional things, or if they are just exceptions to a rule. e.g. if
	i read from a file, the rule is that i get data, and an exception is
	that i reach eof.
	
How is that useful?  *EVERYTHING* is an exception to some rule.
I am having a hard to trying to understand the distinction between
"truly exceptional" and "exceptions to a rule".

Look, the function sqrt() almost never returns 2.6.  So shall we call
returning 2.6 and exception to the rule and require sqrt(6.76) to throw
an exception?  Just because something is an exception to one rule doesn't
mean it doesn't conform to some other rule.  If you try to read from a
file, the rule is that you get data IF THERE IS DATA TO BE HAD, or an
end-of-file indication if there is not.  You apparently prefer the
Fortran rule, where you have to use

    i = 1
    do
        read(Unit, fmt=Format, end=999) a(i)
        i = i+1
    end do
999 ....

because Fortran regards end of file as unusual.  I prefer the C/Lisp/
Scheme/Prolog/Burroughs Algol rule, where end of file is expected and
you write

    i = 0;
    while ((E = scanf(Format, &a[i])) == 1) i++;
    if (E != 0) handle problem

because you simply get a cleaner control structure.

	   hilarious. of course, the difference is the introduction of the ok()
	wrapper. which, IMO, is a lame kludge.
	
It's only "lame" because Erlang is pretty pathetic at combinators.
The Haskell equivalent is perfectly respectable.

	   whatever. seems to me rok has shown that the proper way to use the
	tagged tuple idiom is to write wrappers that removes the tags. and
	even then it is still worse than the exception throwing style.
	
You have left one big thing out.  Exception throwing is a non-local
transfer of control.  It's goto on steroids.  It's not _quite_ as
unstructured as you can get, but it's close.

	but the point is that
	this is the way most existing (good) code looks.
	
	   fold/3 calls Fun/2 with the contents of FileName, broken up in 1000
	   char blocks.
	
	%% BettErl style. fold/3 returns the final Acc or throws.
	%% open/2 and read/2 returns an FD/Data or throws
	fold(FileName,Fun,Acc) ->
	     FD = open(FileName,[read]),
	     try fld(FD,Fun,Acc)
	     after close(FD)
	     end.

	fld(FD,Fun,Acc) ->
	     try fld(FD,Fun,Fun(read(FD,1000),Acc))
	     catch eof -> Acc
	     end.
	
YIKE!  This is HORRIBLE!  It's horrible in too many ways.
Amongst other things, it tacitly assumes that Fun will not throw
an 'eof' exception; the check for eof is in effect associated with
the wrong function call.  Let's suppose that the interface of
read/2 were cleaned up to go like this:

    read(IO_Device, Number) -> binary() | string() | eof
	If more than 0 bytes but fewer than Number remain,
	a short binary or string will be returned.
	If no bytes remain, eof will be returned.
	If IO_Device or Number is inappropriate or something goes
	wrong while reading, an exception will be thrown [details omitted].

Now we have

	fld(Device, Fun, Acc) ->
	    case file:read(Device, 1000)
	     of  eof -> Acc
	      ;  Block -> fld(Device, Fun, Fun(Block, Acc))
            end.

Notice the difference?  The test for 'eof' is visibly associated with
the right function call.  If Fun should happen to throw something, this
code will never mistakenly try to catch it.

Oh, remember that I am *happy* with exceptions for things like
file-doesn't-exist.  SOMETIMES exceptions are right, SOMETIMES
another approach.

One thing I don't know is how cheap it is to set up an exception handler
in Erlang.  In Quintus Prolog, it was fairly cheap, but not zero cost.
The "case" version requires one compare-and-branch per iteration;
if you have C++-style zero-cost handler setup, you can beat that.
But you lose something else: the "case" version is tail recursive,
while the "try" version is not.  In a trivial test case, the setup cost
appeared to be negligible (about 80 CPU instructions).  Well done,
Erlang implementors!  But the space cost of not being tail recursive is
definitely something to think about.

Doubtless I will be savaged for having the temerity to take an example
seriously.

	%% TradErl style
	%% tfold/3 returns {ok,Acc} or {error,R}
	tfold(FileName,Fun,Acc) ->
	     case file:open(FileName,[read]) of
	         {ok,FD} ->
	             case catch tfld(FD,Fun,Acc) of
	                 {'EXIT',R} ->
	                     %% we need this is Fun crashes
	                     file:close(FD),
	                     {error,R};
	                 {error,R} ->
	                     file:close(FD),
	                     {error,R};
	                 {ok,Val} ->
	                     file:close(FD),
	                     {ok,Val}
	             end;
	         {error,R} ->
	             {file_open_error,FileName,R}
	     end.

NO!  This is not good style in anybody's book.
To start with, recall that I do *not* want to defend exceptions for
file opening.  I think that's a case where exceptions are *right*.
But suppose that we use the tagged tuple interface style inappropriately.
It is STILL the case that an error term should say WHAT THE PROBLEM IS,
in particular that a file could not be opened, and which one.  That is,
it is very unhelpful for open(File_Name, Modes) to return {error,posix()}.
It should be
    {error,{open,File_Name,Modes,posix()}}.

Again, recall that I am not concerned to defend the existing body of
Erlang code as such.  I am defending the use of tagged tuples SOME OF THE
TIME.  It so happens that this is NOT one of the uses that I want to defend,
but if one *is* going to return error terms one should d--- well return ALL
the relevant information in the error term.

Also, I have already said that while reaching the end of a file is hardly
something one should be surprised by or unprepared to handle, OTHER errors
in reading SHOULD be exceptions.

So what's the style I advocate for this example?

    fold(File_Name, Fun, Acc) ->
	Device = file:open(File_Name, [read]),
	try
	    fld(Device, Fun, Acc)
	after
	    file:close(Device)
	end.

    fld(Device, Fun, Acc) ->
	case read(Device, 1000)
	 of  eof -> Acc
	  ;  Block -> fld(Device, Fun, Fun(Block, Acc))
	end.

That's right:  exceptions used for things you AREN'T prepared to handle,
appropriate return values for things that you ARE prepared to handle.
Simple, but not simplistic.

Using tagged tuples all the time means that your language doesn't have to
have exception handling at all.  The mess with exit and throw which Mats
has so ably demonstrated in his "TradErl" version of fold/3 is serious
encouragement not to use *old* Erlang exception handling if you could
avoid it.  Hence the interfaces in the library.  (Actually, TradErl
reminds me a whole lot of how you do I/O in Clean.  And I would happily
use Clean still if only they supported it on Solaris.)

Using exception handling all the time isn't that great either.
Do much with Java and you will quickly come to loathe the way that
exception handling distorts your code.  The new 'try' form in Erlang
is a big improvement on what we had before, but even so it is obtrusive
and gets in the way of TRO.

Being totally committed to neither of these, but using now one, now the
other, isn't as easy to think.  It requires careful judgement when
designing the interface of a function.  But that's our job.




More information about the erlang-questions mailing list