On Tue, 2007-01-16 at 13:24 -0600, Edgar Friendly wrote:
> skaller wrote:
> > What about it? C manages to handle all that .. and has no exceptions.
> >
> I think the point here is that because OCaml has exceptions (that other
> people use, maybe not you), it should also have finally blocks to allow
> them to easily do what you do without exceptions.
That is a reasonable argument to examine: but I'm not sure
finally is actually all that useful. If you're thinking of:
try maybe_throw X; do something
finally -> cleanup done
then it can be coded in Ocaml like:
begin try maybe_throw X; do something; cleanup
with x -> cleanup; throw x end
Now you can argue the repetition of 'cleanup' is a bad,
and I would agree, BUT in Ocaml this is no big deal:
begin let cleanup = .. in try ... end
In Java and other brain dead languages without lexical scoping
you cannot do this.
So Java really NEEDS finally, whereas in Ocaml the utility
is less. I note C++ can use RAII:
class file {
FILE * f; file(string x) : f(open(x)) {}
~file() { close(f); }
};
..
{
file f(name); maybe throw ..
}
and here needs no finally, but in general in C++ you have
the same problem as Java: no lexical scoping.
> > But adding 'finally' may not be the best solution,
> > the try/let construction looks better to me.
> >
> I can't find an example of this. Something about doing variable binding
> as part of the try statement, and then finalizing how?
I have lost the URL, sorry. But the problem is localisation:
the only sane way to use exceptions is like:
let result = try Some (open f) with Error -> None in
match result with None -> ... Some ....
If you try to do it like:
try
before
open f
middle
close f
after
with _ -> close f
then you have all sorts of problems if a different exception
is thrown in the before or after parts. Using type information
to distinguish the kind of exception is clearly a hack:
what if you're opening two files?
So roughly, this structure is not amenable to composition.
Adding 'finally' doesn't solve that problem, and that
problem is far worse than the lack of finally.
[Sorry this is a really bad explanation ..]
> > But typically I do that kind of
> > thing the C way, which is generally the Ocaml way too:
> > you propagate return codes from functions using variants
> > up call chains.
> >
> The OCaml standard library seems to be programmed using exceptions over
> variants. I'll have to say I don't like or use exceptions much, but I'm
> forced to by the standard libraries.
Yes, but you can do what many people do: map the exceptions into
variants like:
let hfind x y =
try Some(Hashtbl.find x y)
with Not_found -> None
religiously, eliminating the exceptions. And you can convert
to exceptions from variants like:
let efind x y =
match hfind x y with
| Some r -> r
| None -> raise Not_found
So really the library gives one of two cases, and allows you
to code the other easily. You aren't forced to use "long throw"
handlers, i.e. handlers which are a long way from the point
of the throwing code lexically.
The wrapper above eliminates any need for a finally clause
since ordinary conditionals and matches can be used instead --
in the case you're converting to variants.
It still leaves open the case of "long throw" exceptions
where you want to use exceptions: that may well benefit
from a finally clause but you see now the number of use
cases is reduced because the wrapper technique will be
appropriate in some cases.
IN FACT the problem is not entirely an issue with exceptions!
Examining the 'exception free' case using fopen below
we have the SAME problem:
let fopen f lock = try Some (open f) with Error -> None in
let lck = mutex () in lock mutex;
let f = fopen name lck in
match f with
| Some file -> ... close, release lck
| None -> release lck
WOOPS! Again we had to repeat the cleanup code.
Releasing the mutex AFTER the whole code works, but
if we want separate control flows for the two cases
we have to test the variant again:
begin match f with .. end; release lck;
match f with .. (* fork again *)
So actually, you could use a 'finally' here too: the problem
doesn't appear to be special to exception handling, but rather
a case where you have a control fork and want some code done in
the middle of both branches of the fork.
Roughly .. block structure (nesting) can't handle interleaved
execution very cleanly: you can minimise the impact with
lexically scoped subroutine call, but you still need to call
it twice.
Whereas with 'finally' clause you don't, the problem is
that you're restricted to the balanced (block) structure so the
code location of the 'finally' clause is right for both
branches. In complex cases .. the finally clause isn't
useful because it imposes too much nested structure
that turns out to be wrong anyhow: you have a state machine
which just can't be represented with a pushdown stack,
not even one that supports a second control path via
stack unwinding.
Please note again I'm not arguing against adding finally
(though I might do so!) I'm arguing the need for it isn't
as common as you might think in Ocaml, and part of the need
is only a symptom of a wider problem.
Some other languages provide continuations instead
of exceptions, and it is much easier to manage control
flow in such a language, since in effect 'goto' targets
are first class (can be stored in variables). I guess
it is also easier to *lose* control too :)
--
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net