From: Brian Hurt <bhurt@spnz.org>
To: caml-list <caml-list@inria.fr>
Subject: Today's inflamatory opinion: exceptions are bad
Date: Sat, 9 Dec 2006 20:42:11 -0500 (EST) [thread overview]
Message-ID: <Pine.LNX.4.64.0612091823020.24657@barrayar.nyct.net> (raw)
I think I've come to the conclusion that exceptions are bad.
In Ocaml, they're useless in many cases, and in most cases wrong.
Avoiding them generally makes for better code. There are two vague types
of exceptions- those the program can, and probably should- handle, and
those that the program can't, and probably should even try to, handle.
For the former, returning a variant type ('a option if nothing else) is a
better idea, for (at least) two reasons. One, the type system enforces
the requirement to actually handle the error, at the location the return
value of the function is desired. Want the result? Handle the errors.
Which allow a function to "pass along" an error if it wants to. So you
could still write functions like:
let getline ?channel () =
match filedesc with
| Some(c) -> input_line c
| None -> (* read from stdin *) read_line ()
;;
The second reason is that match ... with doesn't break tail recursion,
while try ... with does. This code is not tail recursive:
let rec echo_file channel =
try
begin
let line = input_line channel in
print_string (line ^ "\n");
echo_file channel
end
with
| End_of_file -> ()
;;
Call this on a very long file, and you'll blow stack. But if input_line
returned, say, string option (where None meant end of file), the natural
code would be:
let rec echo_file channel =
match input_line channel with
| Some(line) ->
begin
print_string (line ^ "\n");
echo_file channel
end
| None -> ()
;;
This is tail recursive. This is probably the number one bug hit by
newbies- "I tried to read in a big file and Ocaml blew up!" The work
around to this is to basically implement the function:
let fixed_input_line channel =
try
Some(input_line channel)
with
| End_of_file -> None
;;
and call it instead. Except often times this function is hand-inlined and
only serves to obfuscate the code.
string option probably isn't the best type for input_line to return, as
reading can cause other errors. But this just proves my point even more:
let rec echo_file channel =
match input_line channel with
| Input(line) ->
begin
print_line (line ^ "\n");
echo_file channel
end
| End_of_file -> ()
| Read_error(desc) ->
begin
print_line ("Read error: " ^ desc ^ "\n");
()
end
;;
Note that this leaves the door open to sharing a single variant type among
all the input functions:
type 'a input =
| Input of 'a
| End_of_file
| Read_error of string
;;
So that input_line returns string input, input_char char input, input_int
int input, and so on.
This does force error handling to be *somewhat* local, but I think this is
a good thing, not a bad thing. The farther away the error handling is
from the error source, the harder it is to figure out what caused the
error, and what to do about it (other than just die). If that's the
behavior I want, it's easy to just do:
| Read_error (_) -> exit (-1)
The other type of exception is exemplified by the out of memory condition.
In 30+ years of using computers and 10+ years of professionally
programming them, I think I've seen two programs that gracefully and
correctly handled the out of memory condition by doing something other
than just exiting the program. And both of those programs assumed the OOM
condition occured because of leaked memory, and thus by rolling back and
starting over memory could be reclaimed. The solution there is to not
leak memory- garbage collection is a wonderfull thing.
As a general rule, if your program is running out of memory, you're trying
to solve too big of a problem. If you're trying to invert a 30k by 30k
matrix on a 32-bit machine, it just isn't going to work. And long before
you run out of memory, you're generally going to push the machine into
swapping and make it unusably slow, so if you're wanting to put a limit on
the size of problems a program can handle, the limit should probably be
well before the out of memory condition happens.
There's one other system level error (i.e. generated by an interrupt) I
can think of the program handling: fp errors, if you're using signalling
fp. Maybe. The thing is that these errors are synchronous, they happen
at specific points in the code and arise because of something specific the
code does. Which means, if you're using these, you want to know where and
why the problem happened- and generally you want to know more than just
"divide by zero on line 29 of mat_invert". And tail call optimization
makes it hard to figure out how you got there in the general case.
I suppose you could want to write code like "normally use conjugate
gradiant, unless you hit a problem, in which case back off and do the much
more numerically stable (if slower) gaussian elimination", with the
definition of "problem" being "hit a signalling float". But this is very
much the exception that proves the rule- this is how rarely exceptions are
usefull.
Other system level errors? Things like bounds check violations, or
integer divide by zeros? These are programatic errors, and generally
catching them in the program doesn't help.
There's one other use for exceptions I've seen (and done)- using them as
non-local longjmps. A classic example for this is a delete functions on a
tree structure- you recurse all the way down to the leafs and discover
that you're not deleting anything at all, what do you do? I sometimes
(too often) throw an exception to jump out of the recursion back up to the
top level.
The fact that I always feel dirty when I do this, and feel the need to
include a comment defending this decision is, I think, indicative ("Hmmm.
This means something!" -- Closet Encounters of the Nerd Kind). Or I could
just be having a Pascal flashback (the language, not the coworker).
My point here is this: Ocaml is not Java (a fact we should all be
gratefull for, IMHO). Simply because Java and C++ do something, doesn't
mean that it's a good thing to do.
Major changes to the language itself I see as unlikely in the near term.
I'm mainly putting this rant out there to a) generate discussion, b) make
people think, and c) maybe influence the design of future Ocaml libraries
and code.
Brian
next reply other threads:[~2006-12-10 1:36 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2006-12-10 1:42 Brian Hurt [this message]
2006-12-10 2:40 ` [Caml-list] " skaller
2006-12-10 2:51 ` Martin Jambon
2006-12-10 3:35 ` Chris King
2006-12-10 6:32 ` Jon Harrop
2006-12-10 19:07 ` brogoff
2006-12-10 18:04 ` Richard Jones
2006-12-10 23:27 ` Chris King
2006-12-11 15:55 ` Richard Jones
2006-12-15 11:13 ` Frédéric Gava
2006-12-11 17:28 ` Mike Lin
2006-12-11 20:09 ` Richard Jones
2006-12-11 23:38 ` Olivier Andrieu
[not found] ` <C841DA73-83D4-4CDD-BF4A-EA803C6D6A08@vub.ac.be>
2006-12-23 4:23 ` Ocaml checked exceptions Chris King
2006-12-10 6:30 ` [Caml-list] Today's inflamatory opinion: exceptions are bad malc
2006-12-10 6:36 ` malc
2006-12-10 6:56 ` Jon Harrop
2006-12-10 9:51 ` Andreas Rossberg
2006-12-10 11:00 ` Tom
2006-12-10 11:25 ` Andreas Rossberg
2006-12-10 13:27 ` Jean-Christophe Filliatre
2006-12-10 19:15 ` Haoyang Wang
2006-12-10 21:43 ` Jean-Christophe Filliatre
2006-12-11 13:10 ` Diego Olivier FERNANDEZ PONS
2006-12-10 18:31 ` Serge Aleynikov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Pine.LNX.4.64.0612091823020.24657@barrayar.nyct.net \
--to=bhurt@spnz.org \
--cc=caml-list@inria.fr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox