[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
ok(process_data(ok(read(ok(open(Name))))))
in BettErl (tm);
process_data(read(open(Name))),
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.
mats
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
conclusion.
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),
close(Channel),
case Outcome
of {ok,Data} -> ok(process_data(Data))
; _ -> ok(Outcome)
end
% exceptions
Channel = open(Name),
try
Data = read(Channel),
after
close(Channel)
end,
process_data(Data)
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