[
Lists Home |
Date Index |
Thread Index
]
On Sunday 23 February 2003 17:34, Karl Waclawek wrote:
> > Many languages have just that construct. "break" is a nonlocal exit, just
> > like throw / catch.
>
> It doesn't unwind the stack.
Depending on the language :-) Most big languages disallow breaks across
procedure boundaries to avoid the issue, but you can escape out of regions
that have their own local variables in.
> > Then you have continuation-passing languages where procedure return
> > points are first class objects; you explicitly pass the return address
> > into a procedure, and there's nothing stopping you from passing in
> > several, one for success and one for each different kind of failure, that
> > return to different points in the stack.
>
> Now that might be a nice feature to have in the popular languages.
> Which language are you talking about?
Hrm... Scheme can do it, that's probably the most accessible example, but
IIRC ML can. At least, the default book on implementing continuation
languages is based around ML. "Compiling Continuations", it's called.
> In principle I can agree.
> I usually look at it that way: If an operation throws an exception
> I consider it failed: Examples:
> - When a constructor does it, I assume the object was not created.
> Making the wrong assumption here will cause a memory leak in languages
> without garbage collection.
> - When I open a file, and that throws an exception, I assume it is not
> open, so I don't need to close/unlock it.
Some languages have the concept (and I don't mean it's part of the syntax,
just part of the conventions of how it's used) of each exception having an
explicit description of what state things will be in afterwards, and what
restarts are applicable.
Restarts?
Well, in Java, an exception handler is executed in the environment it is
declared in, rather than where it's thrown. That means that if you rethrow an
exception from a catch block, the catching of that exception begins in the
stack frame of the try statement, not of the original throw.
But in some languages (Dylan and Lisp come to mind), the exception handler is
executed 'within' the throw statement.
In practice, that means you can do tricks like define, in your file-access
library, an exception called 'FileNotFound' that is thrown by 'File open
(String filename)', and a series of restarts that are legal within a
FileNotFound handler:
1) ReturnThisOpenedFileObject (File file)
2) TryAgainWithThisFileName (String filename)
The restarts work just like exceptions (common base class of Exception and
Restart called, say, Signal); the code for open() might look like:
while (true) {
try {
File f = _real_open (filename);
if (f == null && _errno = ERROR_FILENOTFOUND) {
try {
throw FileNotFound ();
}
catch (ReturnThisOpenedFileObject r) {
f = r.file;
}
}
return f;
} catch (TryAgainWithThisFileName r) {
filename = r.filename;
}
}
Of course, you can still do non-local exits from exception handlers by not
throwing any restarts.
> From this point of view I would assume that an XML document is in error,
> or an external entity could not be resolved, if parsing the document
> throws an exception.
Yep... that's one perfectly valid point of view :-)
But in things like software interfaces, where there is still an element of
style in design as well as logic, other people may have different styles to
us and, sadly, we must be prepared for foreign interfaces to come from
different points of view :-(
> Obviously, it is not always so clear what the definition of a failure is,
> it depends on the kind of operation and possibly the context.
> That is why I tend to not throw exceptions in low level libraries,
> since the context in which the library is used often determines if a result
> of an operation is a failure.
Using an open() call simply to test for file existance is a classic example!
> > Is that under Windows? ISTR that Windows has to handle some part of
> > exception handling itself for some obscure reason and makes it awful
> > slow?
>
> Java seems to be slow too. I tried the same on Kylix/Linux.
> Was actually 10 times worse. So, on Kylix the ratio is more like 1000
> as was reported for Java (don't know which platform).
> Cannot guess as to why.
Yuck!
> > Exeptions don't *need* to be slow. There are fast implementations.
>
> I am curious - which ones?
Well, ones that work in continuation languages... continuation systems, by
not having a stack, eliminate the stack unwinding. Without having the concept
of a procedure return hardcoded into the language, instead having a more
generic construct that implements procedures-that-return, exceptions,
context-switches-for-coroutines and so on, they manage to do all of these
things with about the same speediness.
The cost is that you get more memory allocation and pretty much need garbage
collection, so tricks need to be used to make sure that's done efficiently.
There are issues in how you define the semantics of the pattern matching used
in exception catching, too. A naive implementation of throw would be to walk
up a linked list that represents the dynamic scope of try blocks we are
wrapped by, then invoking some complex algorithm to see if the exception is a
subclass of the things each try wants to catch, which can get time consuming
in itself. However, you can make more of this static; going to the opposite
extreme, statically deduce the set of exception classes that are ever thrown,
then for each thread have an array of linked lists of handlers, one array
element per exception class. Then a try...catch for exceptions that are
subclasses of X would, before entering the protected block, push the handler
onto the linked list for all classes that are subclasses of X. This would
raise the cost of entering a try...catch block, but it would make handling
the exceptions pretty quick!
> How fast can they be - if the problem is inherently more complex,
> then implementations tend to be more complex and slower too.
I think the runtime algorithm required to match the thrown exception to the
correct handler is probably the most inherently complex part. The actual
control transfer can be done most efficiently with a stored failure
continuation. Indeed, for a specific case like stopping SAX processing, you
could just put an 'abort' continuation in thread-local storage before
starting the SAX parser, and just invoke it when needed in the SAX handler :-)
> Karl
--
A city is like a large, complex, rabbit
- ARP
|