[erlang-questions] Package Support/Use

Richard A. O'Keefe ok@REDACTED
Tue Nov 7 03:32:18 CET 2006

Mats Cronqvist <mats.cronqvist@REDACTED> wrote:
	   as a really dumb example, consider three functions open(Filename), 
	read(FileDescriptor), and process_data(Data) that can either fail or return 
	useful data. in C you'd do something like this;
	   if ( ( fd = open(name)) == NULL ) return -1;
	   if ( read(fd,&data) == -1 ) return -2;
	   if ( process_data(&data,&pd) == -1 ) return -3;
	   return 0;
That's pretty unforgivable C:  nowhere does it close the file,
and file descriptors are a resource in fairly short supply.
(And of course open has either 2 or 3 arguments, and read has 3.)
(And of course there are four possible outcomes for
	read(fd, sizeof data, &data)
    : result == sizeof data => success
    : result < sizeof data && result > 0 => partial read;
	if you are reading a known structure from a disc file
	it's probably a bad case, if you are reading from a terminal
	or a tape drive this can be expected on practically every read
    : result == 0 => normal end of file
    : result < 0 => some exceptional problem

	   in Traditional Erlang something like this;
	   {ok,FD} = open(Name),
	   {ok,Data} = read(FD),
	   {ok,PD} = process_data(Data),
Again, that is pretty unforgivable Erlang:  if open/1 creates a resource
which will not be automatically freed.  As has been pointed out on this
list before,  just define

	ok({ok,X}) -> X;
	ok(Error) -> signal some kind of error.

and your Traditional Erlang can become


	   in BettErl (tm);
	   TradErl is more verbose and binds worthless intermediate variables.
Verbose, yes, a little bit.  But it certainly doesn't REQUIRE
you to "bind worthless intermediate variables", that's a straw man.
Traditional Erlang, with the ok/1 function, gives you the CHOICE
of looking at the error result or not, and if you choose not to,
there are NO intermediate variables unless you want them.

	   TradErl has poor error reporting.

	   it will either succeed or fail with "badmatch". not very helpful.

Again, this is only because the example is a straw man.  With the
ok/1 function, you DON'T 'fail with "badmatch"', you get the full
information that the function returned.  So no, The "TradErl" approach
is IN NO WAY INFERIOR with regard to error reporting.

	the BettErl function will either succeed or throw useful stuff
	like {couldnt_open,Name} or {read_failure,enoent}, because that's how
	functions are written in BettErl;

And the Traditional Erlang version with the ok/1 function does exactly the
same thing.  

	   so, to answer your question, tagged tuples encourages verbose
	code with poor error reporting.  in short, bad style.  IMO.
Nope.  All you have demonstrated is that bad code *can* be written in
the traditional style, not that it *must* be.


	p.s. i'm aware there are some oopses in the meta-code (e.g.
	memory leaks).  but i don't think that has any bearing on my

It has a major bearing on your conclusion, which was an argument about
"bad style".  A style that encourages leaks can hardly be called a good
style, now can it?  The score sheet now looks like this:

			Traditional		Throw-happy

More verbose		A little		no
Worse error reporting	no			no
Encourages leaks	no			yes

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.

A regular expression not matching is not exceptional.
A regular expression having bad syntax IS exceptional.
Reaching the end of a file *between* characters is not exceptional.
Reaching the end of a file *within* the UTF-8 byte sequence for a
character IS exceptional.

For me in the open/read/process example, the really important thing is

    - if the file cannot be opened,
      is there likely to be anything the immediate caller can do about it?
      (Probably not:  good case for an exception.)

    - if the read fails, is there anything the caller can do about it?
	A negative result from read(2) counts as exceptional; there is
	probably nothing the program can do about it (except when errno
	is EAGAIN, of course).  
	There is something the caller MUST do, and that is close the file.	

	EAGAIN is a strange special case here; in a language which supports
	lightweight processes we wouldn't need anything like that, so I
	don't want to use it in an argument either wey.
	EINTR is nasty.  I do not believe I have ever seen a C program that
	handles it correctly, and I'm not only not sure what "correctly"
	would mean, to the extent that I think I know, I don't really 
	believe it's POSSIBLE to handle correctly.  Again, if it were
	Erlang all the way down, I don't believe cases like that would exist.

    - if process_data fails, is there anything the caller can do about it?
      Probably not, but does the process_data/1 function know where the	
      problem should be logged?
What does the code *really* look like in these two cases?

      % tagged results

	Channel = ok(open(Name)),
	Outcome = read(Channel),
	case Outcome
	 of  {ok,Data} -> ok(process_data(Data))
	  ;  _         -> ok(Outcome)

      % exceptions

	Channel = open(Name),
	    Data = read(Channel),

Funny how the difference becomes less clear-cut when you make the
example more realistic, isn't it?

Any rate, as the person who designed and implemented exception handling
in Quintus Prolog and single-handedly installed exception reporting
throughout the entire QP system and library, I am not opposed to exceptions
as such.  What I am opposed to is designing in the use of exceptions
without *thinking* about them, and specifically without thinking about
whether a particular outcome really is exceptional or should be handled
as part of normal control flow.  One way to think about it is this:

    if this outcome occurs, would I be happy for the entire calling
    process to drop dead (with a suitable error report)?

If the answer is "yes", then an exception is probably the right thing.

More information about the erlang-questions mailing list