|
From: Twylite <tw...@cr...> - 2008-11-20 10:51:47
|
And there I was thinking this had been discussed on tcl-core ;)
First up, I want to be clear about the intent behind TIP #329:
a) The TIP is about error handling, not exception handling. Tcl has
excellent exception handling which can be readily used in a mostly
non-ugly way (e.g. catch + if/then/else or catch + switch), but its
error handling is poor. Developers use the result (Tcl_SetResult, not
return code) as an error message, representing both the class/category
and exact nature of the error (in a human-readable form), but don't
provide a way to programatically identify the class of error making
control flow based on the type of error a hit-and-miss match on the
error code.
b) The innovation of a new 'finally' that simplifies the linguistic
expression of the programmer's desires has been a large part of my
motivation to develop this TIP.
c) The overall intent of the TIP can be summed up as "make a control
structure that makes dealing with errors and resource cleanup simpler -
both logically and visually".
NEM:
> Firstly, "onerror" and "except" seem like bad names to me. "except" in
> particular would imply that the following error case *isn't* handled (as
> in "catch everything *except* for these..."), which is just confusing.
> I also have some problems with the usage. I'd prefer to see something
> like:
I accept your argument about "except", having had the same concern
myself. I drew this from C's try...except. Others have argued against
the use of 'catch' as it could be confused with the existing catch
command. I'll consider 'handle' instead of catch - it sounds reasonable
for the domain; other suggestions are welcome. I feel that "onerror" is
correctly named though.
> Certainly, I think dispatch based on the return code/exception type
> should be at least as easy as based on errorCode -- I can think of lots
> of examples of Tcl code that does this, but almost none that actually
> looks at errorCode.
While my aim in this TIP was to deal with errors, there has been a
strong call to generalise the control structure to handle exceptions.
My feeling is that it should handle both with equal ease.
> Support for existing, widely-used idioms, should be as much, if
> not more, important than promoting lesser-used alternatives. In fact,
> I'd leave out entirely the glob-matching on errorCode, and let people do
> this with [switch] if they want it:
I cannot agree with this -- it would make this try/catch a control
structure focused exclusively on exceptions (not on errors), and provide
no functional or syntactic benefit over catch+switch (other than a
'finally' clause).
On supporting widely-used idioms, it would be much more important IMO to
branch based on the result than on the return code. Branching based on
return code (i.e. exception handling) is useful for creating new control
structures, but if you want to handle errors then you must branch based
on some information that is available from a return code = TCL_ERROR
(2). That means either errorCode or result, and right now most APIs are
using result.
You make a good point about leaving pattern matching to existing
commands -- I have been thinking along those lines for how best to
exploit that (more in another mail).
> Show me the use-cases! Handling different error-cases with [catch] is
> not that ugly now:
The exact same example switched on the return value of catch, or on
[dict get $opts -code], handles exceptions with the same level (or lack
thereof) of ugliness. Arguably if one does not need a different control
structure for errors, then it is no more necessary for exceptions.
> People don't do it because it just isn't very useful. Errors in Tcl
> tend to be real errors -- other than logging them, there is often not
> much to do. Tcl's introspection, use of general control exceptions
> like [continue]/[break], and custom control structures/HOFs etc make
> this kind of exception-based case analysis much less necessary. I may
> well be wrong about this, but I'd prefer to see some concrete use-cases.
The nature of errors in Tcl is a side-effect of the weak support for
distinguishing between types of errors. This functionality is useful
any time that you are calling into an opaque API and can take different
recovery actions based on the cause of the error. e.g. you want to wrap
load balancing and/or fault tolerance (simplest case: auto-reconnect)
around an RPC or DB interface; you want to try alternative
authentication schemes when the password fails (but not when the
connection fails, or the protocol is mismatched); you want to tell the
user whether to 'try again later' or 'call the administrator'.
Let's drop to Java-speak for a moment: the current practice of Tcl
developers is to "catch (Exception e) { // handle }". In C++ it is
"catch (...) { // handle }".
Return code is not a mechanism for exception/error typing, it is a
mechanism for implementing control flows. We need a typing mechanism.
JE:
> I would suggest:
>
> try {
> ...
> } onerror {pattern ?resultVar ?optionsVar??} {
> #
> # return code was TCL_ERROR, errorCode matches $pattern
> #
> } handle {code ?resultVar ?optionsVar??} {
> #
> # return code was $code.
> #
> } finally {
> ...
> }
>
Based on NEM's argument against 'except', this is pretty much what I'm
left with (with 'then' as an alias for 'handle ok').
>> > I still have concerns about this though:
>> > - The construct is getting pretty bulky - is it really adding power &
>> > simplicity as it is intended to do?
>>
> Yes. Dispatching based on the return code is essential
> for creating user-defined control structures.
>
There seems to be a consensus that the try/catch structure needs to
handle return codes (at least as easily at errors).
>> > The only real question is whether
>> > the msgvar and optvar should be that way round. Pass on that! :-)
>>
> Yes, they are in the right order.
>
Agreed - the order {emvar optsvar} is consistent with catch.
> I believe so. Nested try/finally clauses -- one for each
> resource that needs to be cleaned up -- all wrapped in
> an outer try/catch for error recovery would cover
> all the exception handling needs I can think of.
>
> I don't like the alternative approach much either,
> simply because it smells too much like innovation.
>
And there I was thinking that Tcl generally supported innovation ;p
That troll-hole aside, I do a lot of work in C, especially firmware for
embedded systems. Not having syntax for exception handling, there are
two approaches you can take to code flow and resource cleanup:
1) Deeply nested logic. Your "ideal path" reads diagonally as you
acquire resources and check conditions in nested if/then/else
statements. There is one point of return at the end of the function.
2) Fail-fast logic. Your code reads down the page; when an error is
encountered to jump (goto) a cleanup block at the end of the function.
Your code never indents for error codes, only for non-error branches.
Although the only return is at the end of the function this is a
consequence of the lack of try/finally syntax - in effect each 'goto' is
a point of return.
If you've never encountered non-trivial functions written in these two
styles, I suggest you take a look. The latter is (by my reckoning and
that of all experienced developers I've worked with) far more readable,
understandable and maintainable. YMMV, as may your opinion.
The whole point of exception handling (by which I mean
Java/C++/Python/Ruby/whatever, not Tcl's
use-a-return-code-for-flow-control) is to support the fail-fast paradigm
in conjunction with not having to explicitly check for failure on each
call (exceptions branch the flow at the point of the call and propagate
up the stack). If your code must still be deeply nested in order to
(safely) handle cleanup then are you really gaining anything from
exception handling? Your code remains visually complex, and the benefit
of the new syntax is limited.
Looking at this another way, you _could_ use nested try/finally clauses
in Java, C++, etc. to avoid the common bugs in finally clauses (freeing
an unallocated resource, not handling exceptions off resource
deallocation leading to dangling resources) but _developers don't_. It
could be laziness, incompetence, lack of care, lack of testing, not
having been caught by the bugs enough, not being aware of the problem,
or simply that its too much of a pain to do it properly. I don't know
for sure, but from my interactions with developers in my company I'm
leaning towards the last reason.
> There has been a longstanding desire that people [*] make
> better use of -errorcode, but there's a vicious cycle:
> nobody bothers to use meaningful -errorcodes because nobody
> bothers to check -errorcode because there's no convenient
> way to do so.
>
> try/onerror would at least break that part of the cycle.
>
This is the intention of the TIP, yes.
Regards,
Twylite
|