| 
     
      
      
      From: Twylite <tw...@cr...> - 2008-11-19 09:18:04
       
   | 
Hi,
>> >    TIP #329	Try/Catch/Finally syntax
>>     
>
> I'd really like to see this in 8.6.  However, I also think
> it's essential that try/catch be able to dispatch based on
> the return code, not just $::errorCode, which the current
> specification does not provide for.  Please fix!
>   
Although I haven't updated the spec, the current proposal is:
  try {
    #code
  } then {
    #success continuation
  } onerror {glob emvar optsvar} {
    #handle errors based on errorCode
  } except {spec emvar optsvar} {
    #handle errors based on any matching information in optsvar
  } finally {
    #always execute this as the last action before leaving the try command
  }
The "except" clause will handle exceptions (non-zero Tcl return code) as 
opposed to errors (Tcl return code TCL_ERROR with dispatch based on 
errorCode).  Not certain on what the "spec" in the except clause is 
going to look like though.  Suggestions welcome (via e-mail or to 
http://wiki.tcl.tk/21608).
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?
- The "except" clause needs to be flexible enough to handle the expected 
common use cases, but not so flexible/complex that it's easier to just 
use catch.  There is little clarity on what the "common use cases" are.
- Often information about an error is implied in the result (as in 
Tcl_SetResult) rather than an errorCode -- can except (or some other 
clause) adequately handle this case?
I'm still investigating the syntax in other languages to what we can 
cherrypick ;)
A modification I find particularly interesting is an alternative 
approach to finally.  The current approach - while typical for try/catch 
syntax - is often a source of subtle bugs.  It is common for a finally 
to have to check each resource before cleaning it up (and equally common 
for checks to be missed or assumptions made ... e.g. if { $somevar ne {} 
} { # cleanup } but an exception was thrown before somevar was set. 
A nested try/then/finally may go some way to address this situation, but 
there are more interesting approaches, e.g. a "finally /script/" command 
that can be placed anywhere in a block of code, and queues script for 
execution at the end of the block of code.  This approach is cleaner (no 
"finally" clause adding bulk to the try/catch syntax), and resource 
allocation and cleanup concerns can be located together in the source 
code (locality is a good thing).  Error/exception handling is also 
simplified as each finally script runs independently (exception 
information is chained) - so if part of your 'finally' fails, other 
parts will still run without having explicit exception handling in a 
'finally' clause.
Simple example of alternate finally syntax:
try {
  set f [open "myfile.txt" r]
  finally [list close $f] ;# or finally { close $f } since it executes 
in the context of the block
  # ...
}
Anyway ... there is still work to be done ;/
Regards,
Twylite
 | 
| 
     
      
      
      From: Donal K. F. <don...@ma...> - 2008-11-19 10:06:32
       
   | 
Twylite wrote:
> 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?
Why are you using all of it at once? That's its most bulky case. (It's
also something that's quite a PITA to write with just [catch], speaking
as someone who's done that in the past...)
> - The "except" clause needs to be flexible enough to handle the expected 
> common use cases, but not so flexible/complex that it's easier to just 
> use catch.  There is little clarity on what the "common use cases" are.
So long as we can do multiple 'onerror' clauses and can omit the capture
variables, we'll be OK. (The errorinfo and errorcode can be picked out
of the options.) For the 'except' clause, it's probably only necessary
to be able to match the 'code' itself (and optionally capture message
and options, of course) while allowing the normal aliases for 0-4 (i.e. 
ok, error, return, break, continue).
It's possibly a good idea to forbid the trapping of 'ok' and 'error' 
using the 'except' clause; they have their own syntax.
The other thing is that the various trap clauses should be arbitrarily 
reorderable.
> - Often information about an error is implied in the result (as in 
> Tcl_SetResult) rather than an errorCode -- can except (or some other 
> clause) adequately handle this case?
Wrong question. The right question is "should it handle the case?" and I
think the answer is "no". Let's clean up the problems with critical info
not going into the errorcode instead (and I know that might take a bit).
> A modification I find particularly interesting is an alternative 
> approach to finally.
[...]
> Simple example of alternate finally syntax:
> try {
>   set f [open "myfile.txt" r]
>   finally [list close $f] ;# or finally { close $f } since it executes 
> in the context of the block
>   # ...
> }
I don't like that nearly as much, FWIW.
Donal.
 | 
| 
     
      
      
      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
 | 
| 
     
      
      
      From: Neil M. <ne...@Cs...> - 2008-11-20 13:40:54
       
   | 
Twylite wrote:
> 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.  
I don't agree that exception handling is excellent in Tcl currently.
> 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.
I believe this is already addressed by the presence of errorCode. 
Pattern-matching against this code is already quite simple, with 
[switch] and so on. It just hasn't caught on. It seems like wishful 
thinking to expect that it will suddenly catch on just because its usage 
is made slightly more convenient (we're talking about a reduction of 
about 1 line of 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.
I agree with the motivation for this entirely.
> 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".
I think it is worth explicitly pulling out the separate concerns 
embodied in this TIP and tying them to use-cases. As I see it, there are 
a number of more-or-less separable concerns here:
1. Support for ensuring resource cleanup in the presence of 
exceptions/errors (i.e. "finally").
2. Better support for case-analysis based on exception return code.
3. Better support for case-analysis based on errorcode.
4. Ability to trap only those exceptions/errors that you are interested 
in/prepared to deal with, letting others propagate normally.
Now, it we look at some use-cases and how they are handled currently, 
versus proposed methods for handling them:
1. Guaranteed resource cleanup
------------------------------
A typical example of this is ensuring that a channel is closed once we 
have finished processing it. Currently, you would do this via:
  set chan [open $myfile r]; # or [socket] etc
  catch { process $chan } msg opts
  close $chan
  return -options $opts $msg
Some things to notice here are: (a) the catch-all behaviour of [catch] 
is exactly what is required here: we don't want any exceptions to 
escape, (b) we don't require any case-analysis on the exception type or 
error code. The proposed alternative is:
  set chan [open $myfile r]
  try { process $chan } finally { close $chan }
This is a saving of two lines, but I think the improvement in 
readability is worth it. A possible improvement would be for any errors 
in the finally clause to not completely mask any errors in the main 
script -- perhaps by adding a -original field to the options dict (which 
itself contains the message and options dict of the original error).
I'd be happy to see this part of the TIP dropped or separated into a 
different command however.
2. Case-analysis based on return code
-------------------------------------
An example here is that of implementing custom control structures. Of 
particular interest here is the use of standard exceptions like [break] 
and [continue]. For instance, we may want to write a version of 
[foreach] that operates asynchronously using the event loop. Currently, 
we might write this as:
  proc async-foreach {varName list body} {
      if {[llength $list] == 0} { return }
      set list [lassign $list item]
      set code [catch { apply [list $varName $body] $item } msg opts]
      switch $code {
          3       { return }
          0 - 4   { # ok/continue }
          default { return -options $opts $msg }
      }
      after 0 [list async-foreach $varName $list $body]
  }
(I can never remember whether you need to [dict incr opts -level] in 
these situations).
The try/handle (Twylite/JE) alternative would be:
  proc async-foreach {varName list body} {
      if {[llength $list] == 0} { return }
      set list [lassign $list item]
      try {
          apply [list $varName $body] $item
      } handle {code msg opts} {
          switch $code {
              3       { return }
              0 - 4   { # ok/continue }
              default { return -options $opts $msg }
          }
      }
      after 0 [list async-foreach $varName $list $body]
  }
This is slightly longer and introduces more nesting than the original. 
In general, it seems to hamper rather than improve readability. By the 
way, does "handl" catch all exception types, or just non-errors? In 
general, try/handle seems just a more verbose version of the existing 
[catch].
The alternative using try/catch would be:
  proc async-foreach {varName list body} {
      if {[llength $list] == 0} { return }
      set list [lassign $list item]
      try {
          apply [list $varName $body] $item
      } catch break {} { return } catch continue {} {}
      after 0 [list async-foreach $varName $list $body]
  }
This scheme is shorter. It is more readable as we have symbolic names 
"break" and "continue" rather than magic numbers, and it avoids catching 
anything it doesn't know how to deal with.
Note that dispatch based on exception code is a simple branch. There is 
no need for complex pattern matching, sub-match capture, or 
case-insensitive matching.
3. Case-analysis based on errorcode
-----------------------------------
For this, I'll use Twylite's example of trying different authentication 
schemes. We will assume that the API throws an error with code BADAUTH 
when the wrong scheme is used, and throws other errors such as NOCONN to 
indicate that a connection to the host failed. (Note: this example 
doesn't require glob-matching, but I don't think the changes in such a 
case are that great). The existing way to handle this would be:
  proc connect {schemes host user pass} {
      foreach scheme $schemes {
          if {[catch { $scheme connect $host $user $pass } res opts]} {
              switch [dict get $opts -errorcode] {
                  BADAUTH  { continue }
                  default  { return -options $opts $res }
              }
          } else { return $res }
      }
      error "unable to authenticate"
  }
Using the proposed try/onerror approach, this would be:
  proc connect {schemes host user pass} {
      foreach scheme $schemes {
          try {
              return [$scheme connect $host $user $pass]
          } onerror BADAUTH { continue }
      }
      error "unable to authenticate"
  }
Using try/catch:
  proc connect {schemes host user pass} {
      foreach scheme $schemes {
          try {
              return [$scheme connect $host $user $pass]
          } catch error {msg opts} {
              switch [dict get $opts -errorcode] {
                  BADAUTH  { continue }
                  default  { return -options $opts $msg }
              }
          }
      }
      error "unable to authenticate"
  }
OK, in this case the try/onerror approach certainly is clearer, and the 
try/catch approach is little better than the existing way with [catch] 
alone. I think it still wins slightly over [catch] in readability. While 
it is more verbose, the control flow is easier to read -- the [return] 
is in an obvious place, and not tucked away in an inconspicuous "else" 
clause. There's also less punctuation. But the onerror approach is 
clearly better for this case.
The questions then, are whether "onerror" is sufficient for all/most 
such cases, and whether these cases actually arise often/ever in 
practice (or would arise given appropriate promotion). Regarding the 
first part, dispatching based on errorcode is more complex than based on 
return code as while the latter is a simple integer, the former can be 
an arbitrarily complex data structure. In particular, the following 
requirements may have to be considered:
  a. Different forms of pattern-matching (e.g. exact, glob, regexp, 
"algebraic" type matching etc). If we stick to one type only, will that 
be appropriate? Will it cause problems? (e.g. if glob-only matching, 
then we have problems specifying glob-special characters such as * or []).
  b. Case sensitivity -- is "arith" the same error as "ARITH" or "ARiTh"?
  c. Sub-match capture: an errorcode may contain detail fields which we 
want to extract. It seems pointless to match once and then perform a 
separate extraction when I could have just used [regexp] or some other 
matching facility and performed both operations in one go.
  d. Disjunctive matching: perform this action if the errorcode matches 
either *this* or *that*.
I'm sure there are others. To me, the range of choices here suggests 
that pattern matching is best kept separate from error-handling. 
Otherwise there is a risk of duplicating [switch]. Perhaps I am wrong 
here, and glob-matching meets all requirements. Personally, if errorcode 
matching was to take off, I would use some form of algebraic types 
(tagged lists) both for constructing and pattern-matching errorcodes, as 
that seems to me to be the most appropriate tool for the job. Perhaps 
there is a way to keep the behaviour but to parameterise the matching 
command (with switch -glob/string match being the default).
The other question is whether these cases arise in practice. I can't 
think of a single existing API that requires this kind of errorcode 
pattern matching. Is such a design even appropriate? Clearly, if you 
controlled the authentication scheme interface then you could just 
return continue for BADAUTH and an error for anything else:
  proc connect {schemes host user pass} {
      foreach scheme $schemes {
          return [$scheme connect $host $user $pass]
      }
      error "unable to authenticate"
  }
4. Trapping only those errors/exceptions you are interested in
--------------------------------------------------------------
It's clear from the above that the Twylite/JE approach achieves this for 
errors, but not for other exceptions. My approach achieves it for 
exceptions but not for more specific error cases.
> 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.
I'd really prefer things to be unified in name at least: "on error", "on 
break", etc. One possible unification that might please all would be to 
adopt the syntax "on exception-type ?pattern? ?vars? body". The pattern 
is an optional glob-style pattern that is matched against the -errorcode 
of the exception. (If the pattern is specified then so must the vars). 
Clearly this is mostly useful in the case of errors, but I believe it is 
possible for non-error exceptions to also set -errorcode, so it might be 
useful elsewhere. That would result in the following use-cases:
  proc connect {schemes host user pass} {
      foreach scheme $schemes {
          try {
              return [$scheme connect $host $user $pass]
          } on error BADAUTH {} { continue }
      }
      error "unable to authenticate"
  }
  proc async-foreach {varName list body} {
      if {[llength $list] == 0} { return }
      set list [lassign $list item]
      try {
          apply [list $varName $body] $item
      } on break { return } on continue {}
      after 0 [list async-foreach $varName $list $body]
  }
To me this seems like a good compromise, and people who want more 
complex pattern matching can still do so. I'd like to be able to support 
lists of exception types. I still don't believe glob-style errorCode 
pattern matching is useful or particularly satisfactory, but I'm willing 
to concede it for the sake of compromise. As before, define "then" as 
"on ok", and possibly define "else/otherwise" as a catch-all default clause.
<aside>
I also see this meshing nicely with a hypothetical future 
continuation-based exception mechanism that allows 
resumable/non-destructive exception/warning notifications.
</aside>
[...]
> 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).
Branching based on the result just seems like a fragile nightmare. 
Localised error messages for instance could totally change the behaviour 
of code.
[...]
>> 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'.
Thanks for these use-cases.
> 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.
Hmmm... error handling needs case-analysis not typing per se. Clearly, 
any mechanism used for constructing error cases is strongly related to 
the mechanism that is used for distinguishing them, which is why I'd 
rather see these aspects separated/parameterised from the exception 
handling, at least until it is clear what the best mechanism for this is.
[...]
-- Neil
This message has been checked for viruses but the contents of an attachment
may still contain software viruses, which could damage your computer system:
you are advised to perform your own checks. Email communications with the
University of Nottingham may be monitored as permitted by UK legislation.
 | 
| 
     
      
      
      From: Donal K. F. <don...@ma...> - 2008-11-20 16:41:10
       
   | 
Neil Madden wrote: > I believe this is already addressed by the presence of errorCode. > Pattern-matching against this code is already quite simple, with > [switch] and so on. It just hasn't caught on. It seems like wishful > thinking to expect that it will suddenly catch on just because its usage > is made slightly more convenient (we're talking about a reduction of > about 1 line of code). Also a reduction in the nesting depth. That reduces user-visible complexity. That's a good bonus. > 4. Trapping only those errors/exceptions you are interested in > -------------------------------------------------------------- > > It's clear from the above that the Twylite/JE approach achieves this for > errors, but not for other exceptions. My approach achieves it for > exceptions but not for more specific error cases. The problem with making more use of exception codes is that they're a global resource; they're not easy to restrict to a single interpreter. That makes them significantly less suitable for use in packages, and hence restricts their general applicability rather a lot. They've also got hard-coded traceability behaviour (i.e. none). > Branching based on the result just seems like a fragile nightmare. > Localised error messages for instance could totally change the behaviour > of code. Agreed. And localizing error messages is messy too without in effect the effort required to generate errorcodes properly. Which in turn makes the error trapping proposed viable and useful. :-) Donal.  | 
| 
     
      
      
      From: Joe E. <jen...@fl...> - 2008-11-20 18:39:30
       
   | 
Neil Madden wrote:
> The try/handle (Twylite/JE) alternative would be:
>
>   proc async-foreach {varName list body} {
>       if {[llength $list] == 0} { return }
>       set list [lassign $list item]
>       try {
>           apply [list $varName $body] $item
>       } handle {code msg opts} {
>           switch $code {
>               3       { return }
>               0 - 4   { # ok/continue }
>               default { return -options $opts $msg }
>           }
>       }
>       after 0 [list async-foreach $varName $list $body]
>   }
A clarification: this is not what I proposed in [1].
In a clause like:
    handle {code ?resultVar ?optionsVar??} { script }
"code" would be an integer or a literal "ok"/"error"/etc.,
not a variable name.  Same as the
    catch code {?resultVar ?optionsVar??} { script }
form you proposed, just spelled slightly differently.
What I had in mind would look like:
    try {
	apply [list $varName $body] $item
    } handle return {
	return
    } handle ok {
    	# no-op
    } handle continue {
    	# no-op
    }
The clause "default { return -options $opts $msg }" from
the first script is unneeded, since that's the default
behaviour of [try] when no "handle" or "onerror" clause
matches.
--Joe English
[1] <URL: http://sourceforge.net/mailarchive/message.php?msg_name=E1L2s55-0001xW-6L%40eurydice.office.flightlab.com >
 | 
| 
     
      
      
      From: Magentus <mag...@gm...> - 2008-11-22 03:49:54
       
  
        
          
            Attachments:
            signature.asc
          
        
       
     | 
On Thu, 20 Nov 2008 10:39:26 -0800, 
Joe English <jen...@fl...> wrote:
>     handle {code ?resultVar ?optionsVar??} { script }
Is there any actual practical use to putting code in the braces?  The
variables, I don't think can be avoided in this style, although I'm not
sure that there is a need to have every single branch specify the
variables individually.  I'd much prefer to have them specified once at
the top (and optionally at that) rather than repeated monotonously on
every single branch.....
Something like a:
    withvars {resultVar ?optionsVar?}
following the main try script indicating where to stash the variables.
(I did mention that in my last post that hasn't yet been passed through)
Although personally I'd prefer a -vars option to [try], despite the
obvious issue of not being allowed to have a command called -vars that
_takes no arguments_.  Is anyone seriously going to try and insist
that that's a big issue?!?  If it's a variable name, then fine, it's a
very realistic problem, but what static script is going to non-obviously
conflict (the main use-case), and for passed in scripts through
variables, just use the -- guard option.  From using [switch], it's
common to put -- before variables anyhow.  I know a "Better Way" would
be preferably, but it's the least ugly and most practical so far.
In any case, a nice clean simple layout seems to be:
    handle pattern {...}
where the return code is TCL_ERROR and pattern is glob matched against
errorcode.
    on code pattern {...}
now pattern matches against the return value, even if code is
TCL_ERROR.  More usefully, it'll gracefully handle applications which
have multiple kinds of success, as well as multiple kinds of error,
and can handle both regular bare [error] returns and the use of
errorcode with equal ease.
Supporting all the different types of match, though, would be a "Good
Thing".  -glob, -nocase, -exact, -prefix, -regexp, and so forth...
That's why I think the next part is worth giving a second thought.
For the blending with [if] option, there was chatter a while back about
fast [expr]-local variables intended mostly to hold partial results
during an expression; the main terms of the options dict could quite
readily be pre-loaded as [expr]-local variables.
A little care might be needed to avoid shadowing the variable you wish
to use to hold the value to compare, but that should be doable.
Perhaps [expr]-local variables should use a %syntax synonymous with the
regular $syntax for variables to avoid name clashes (and highlight
the fact that they're [expr]-local).
All this could further be put off by implementing it as a separate
[try] term later, for when extended matching is needed. Then you've got
"handle" and "on" for just about everything, and "when" or even "if"
for anything else.  (Imagine an ambiguous case among more readily
matchable cases, that would otherwise force you to break the [try] into
a much less easy to read chunk of code.)
-- 
Fredderic
Debian/unstable (LC#384816) on i686 2.6.23-z2 2007 (up 44 days, 21:32)
 | 
| 
     
      
      
      From: Neil M. <ne...@Cs...> - 2008-11-22 14:08:36
       
   | 
On 22 Nov 2008, at 03:49, Magentus wrote:
> On Thu, 20 Nov 2008 10:39:26 -0800,
> Joe English <jen...@fl...> wrote:
>
>>     handle {code ?resultVar ?optionsVar??} { script }
>
> Is there any actual practical use to putting code in the braces?  The
> variables, I don't think can be avoided in this style, although I'm  
> not
> sure that there is a need to have every single branch specify the
> variables individually.  I'd much prefer to have them specified  
> once at
> the top (and optionally at that) rather than repeated monotonously on
> every single branch.....
Agreed on both counts.
> [...]
> Supporting all the different types of match, though, would be a "Good
> Thing".  -glob, -nocase, -exact, -prefix, -regexp, and so forth...
> That's why I think the next part is worth giving a second thought.
If these are supported it should be only by explicit delegation to  
[switch], rather than making all these options part of the interface  
of [try] too.
I proposed the following interface offline to Twylite (slightly  
altered) yesterday:
     try ?-matchcommand cmd? script ?handlers ...? ?finally script?
Where -matchcommand is the command to use to do errorCode matching  
and defaults to {switch -glob --}. (May need a -- marker to eliminate  
ambiguity). The syntax of the handlers part would be:
     on exception-types ?vars? ?errorPattern? body ?errorPattern  
body ...?
Where exception-types is a list of one or more of "ok", "error",  
"return", "break", "continue" or an integer return code. errorPattern  
is a pattern (specific to the -matchcommand) used to match against  
the -errorcode (if any). ?vars? is a list of var names {?resultVar ? 
optsVar??} and finally "body" is a script. If you want to specify the  
errorPattern you must also specify the vars, to avoid ambiguity. If  
you want to specify multiple patterns, then the first errorPattern is  
mandatory. The try command then builds a lookup table mapping return  
code -> (ordered) dict of patterns/scripts. It does a simple lookup  
based on the return code of the initial script and then passes the  
errorCode and dict of patterns to the -matchcommand, i.e. it would  
become [switch -glob -- $errorCode $handlers]. If there is no  
errorCode (e.g. a non-error exception) then the empty string is  
passed. A "default" branch is also added to the end of the handlers  
which just rethrows the exception, or if a branch with no pattern is  
specified then this becomes the default branch. The body of a handler  
can be "-", with the same meaning as in [switch]. To be concrete, the  
syntax would look something like:
     try {
         ...
     } on error {msg opts} {IO *} - {POSIX *} {
         ...
     } {ARITH *} {
         ...
     } on {break continue} {
         ...
     } finally {
         ...
     }
You could also write those "on error" clauses as:
     } on error {msg opts} {IO *} - {POSIX *} {
        ...
     } on error {msg opts} {ARITH *} {
        ...
     }
Or any combination of the styles you prefer (the former allows you to  
specify the vars once, the latter may be more readable). The core  
dispatch then becomes something like:
     set rc [catch { $script } msg opts]
     invoke 1 {*}$matchcmd [dict get $opts -errorcode] [dict get  
$handlers $rc]
As before, "then" could be taken as sugar for "on ok", and some catch- 
all "else/otherwise" could be added.
It seems to me that this covers most (all?) use-cases, while still  
keeping [try] relatively minimal and efficient. In particular, [try]  
avoids acquiring the interface of any particular pattern matching  
construct. It can also support various types of matching (e.g. I can  
use algebraic types, OO enthusiasts can use sub-class matching, and  
others can use arbitrary expressions). The core [try] command only  
needs to implement basic return-code based matching, which is O(1),  
so remains efficient for those implementing control structures over  
the top of this. Examples of customisation:
   interp alias {} datatry {} try -matchcommand {datatype match}
   interp alias {} regtry  {} try -matchcommand {switch -regexp - 
nocase --}
   interp alias {} exptry  {} try -matchcommand expmatch
   proc expmatch {code cases} {
       foreach {case body} $cases {
           if {$case eq "default" || [expr $case]} { return [uplevel  
1 $body] }
       }
   }
   exptry { ... } on error {msg opts} {[lindex $code 2] > 1000} { ... }
Thoughts? I'm sure the syntax may need some jiggling to get order of  
things optimal and to eliminate any remaining ambiguities.
-- Neil
This message has been checked for viruses but the contents of an attachment
may still contain software viruses, which could damage your computer system:
you are advised to perform your own checks. Email communications with the
University of Nottingham may be monitored as permitted by UK legislation.
 | 
| 
     
      
      
      From: Magentus <mag...@gm...> - 2008-11-23 13:29:27
       
  
        
          
            Attachments:
            signature.asc
          
        
       
     | 
On Sat, 22 Nov 2008 14:06:59 +0000, 
Neil Madden <ne...@Cs...> wrote:
>> Supporting all the different types of match, though, would be a
>> "Good Thing".  -glob, -nocase, -exact, -prefix, -regexp, and so
>> forth... That's why I think the next part is worth giving a second
>> thought.  
> If these are supported it should be only by explicit delegation to  
> [switch], rather than making all these options part of the interface  
> of [try] too.
I've had the impression for a while now that TCL needs a generic
extensible matching framework that can be plugged into any command.
Not gonna make it into 8.6, but might be worth considering for the
future of not only [try] but the rest of TCL also.  My thoughts have so
far run something like this;
Start with a Tcl_Parse_Match_Options() building a struct that can be
passed to a matching Tcl_Perform_String_Match() function to match two
strings with previously specified options.  TPMO() would accept any
option not already recognised (or vice-versa), and if it's one of its
options, consume it and add it into the current matching options struct
(allowing the options to be inter-mixed with those of the surrounding
command).
To alleviate the problem of option bloat, a good deal of them could
perhaps be eventually phased out in deference to a single -match option
(-glob, -exact, -prefix, -regexp, etc.), allowing new match functions
to be added to a ::tcl::match namespace, and picked up automatically by
every command using the matching framework.  The modifiers (such as
-nocase) would become options to the -match options value.
    eg. -match {glob -nocase}
The commands in ::tcl::match would ALL support extraction of the
matched regions, ala [regexp].  But they would ONLY support the -index
method (for efficiency), returning the start and stop of each matched
section.  The enclosing command (or a secondary helper) would be
responsible for extracting the actual corresponding strings.
A second-layer matching framework could well extend it a step further
to also pick up on the grouping options, index options, and similar,
reducing [lsearch] to nothing but a very simple wrapper, and allowing
an [lmatch] command that walks two lists, instead of one list and a
static value, in the same manor.
Further, the -match method could be "complex", indicating that the match
arguments are themselves distinct -match values (which in turn may be
nested "complex" descriptions), allowing it to handle deep matching of
any repeated regular nested list structure.
Finally, the original TPMO() function (and the complex match method)
can look at the match it's being asked to use, and if it's one of the
built-in matches, obtain the core match function to be invoked
directly.  For other non-built-in matches, it'd invoke a wrapper that
in turn invokes the specified TCL command, though with as much of
the pre-invocation setup as possible already performed to hopefully
alleviate some of the -matchcommand slow-down.
-- 
Fredderic
Debian/unstable (LC#384816) on i686 2.6.23-z2 2007 (up 46 days,  7:13)
 | 
| 
     
      
      
      From: Joe E. <jen...@fl...> - 2008-11-20 18:18:39
       
   | 
Twylite wrote:
>
> 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),
[ FWIW, I don't fully agree.  TIP#89 made correct exception
  handling _possible_, but it's still not very convenient or pretty.
  That's why I think it's essential that TIP#239 support exception
  handling as well, not just error handling.  Especially since it
  can easily do so with only minor changes. ]
> but its error handling is poor.
Full agreement here.
> > I don't like the alternative approach [to try/finally] much either,
> > simply because it smells too much like innovation.
> >
> And there I was thinking that Tcl generally supported innovation ;p
Again: I think the alternative approach sounds interesting
and is worth pursuing, just *not in this TIP*, and *not right now*.
We have a short deadline for 8.6.
[Elsewhere]
Neil Madden wrote:
[ re: error handling ]
> I believe this is already addressed by the presence of errorCode.
> Pattern-matching against this code is already quite simple, with
> [switch] and so on. It just hasn't caught on. It seems like wishful
> thinking to expect that it will suddenly catch on just because its usage
> is made slightly more convenient (we're talking about a reduction of
> about 1 line of code).
Some anecdotal evidence, FWIW: reviewing some old code I
came across two places where I was initially tempted
to use meaningful -errorcodes, but then thought
"Why bother?  I'm never going to look at them,
the [if {[catch { }]} { switch $::errorCode { ... } }]
idiom is utterly unperspicuous," so I didn't.
Had a nice try/onerror construct been available,
I would have.   So I don't think the hypothesis
is entirely without merit :-)  Sure, it's only
a reduction of about 1 line of code, but that 1
line is *ugly* code.
--Joe English
  jen...@fl...
 | 
| 
     
      
      
      From: Michael S. <sc...@un...> - 2008-11-20 20:16:26
       
   | 
Am 20.11.2008 um 19:18 schrieb Joe English:
> Some anecdotal evidence, FWIW: reviewing some old code I
> came across two places where I was initially tempted
> to use meaningful -errorcodes, but then thought
> "Why bother?  I'm never going to look at them,
> the [if {[catch { }]} { switch $::errorCode { ... } }]
> idiom is utterly unperspicuous," so I didn't.
Grepping inside Tcllib also shows rather weak usage of -errorcode.
Some of the network protocols (LDAP, SPF), ASN, wip, some math  
subpackages, comm.
Michael
 | 
| 
     
      
      
      From: Kevin K. <ke...@ac...> - 2008-11-21 13:17:40
       
   | 
Joe English wrote:
> Some anecdotal evidence, FWIW: reviewing some old code I
> came across two places where I was initially tempted
> to use meaningful -errorcodes, but then thought
> "Why bother?  I'm never going to look at them,
> the [if {[catch { }]} { switch $::errorCode { ... } }]
> idiom is utterly unperspicuous," so I didn't.
> Had a nice try/onerror construct been available,
> I would have.   So I don't think the hypothesis
> is entirely without merit :-)  Sure, it's only
> a reduction of about 1 line of code, but that 1
> line is *ugly* code.
I think it might be informative to separate the discussion
of "meaningful errorcodes" from try/catch/finally, because
I want meaningful errorcodes for an entirely different reason.
For error messages that are likely to reach an application's
user, there is likely to be a desire for localisation.
The commonest case is filesystem errors, where presenting
the OS status ("file not found", "no permission", "I/O
error", etc.) appropriately translated makes sense. Right
now, that's incredibly hard; you have to parse the error
message and write a new one.  It would be nice to have
at least *some* error codes that can hook into msgcat
gracefully.
-- 
73 de ke9tv/2, Kevin
 | 
| 
     
      
      
      From: <dg...@ni...> - 2008-11-21 18:26:04
       
   | 
Quoting "Kevin Kenny" <ke...@ac...>:
> The commonest case is filesystem errors, where presenting
> the OS status ("file not found", "no permission", "I/O
> error", etc.) appropriately translated makes sense. Right
> now, that's incredibly hard; you have to parse the error
> message and write a new one.
I agree with your reasoning, but you've chosen bad examples.
I/O errors are some of the few that actually set meaningful
-errorcode values:
% catch {open no-such} m o
1
% dict get $o -errorcode
POSIX ENOENT {no such file or directory}
DGP
 | 
| 
     
      
      
      From: Magentus <mag...@gm...> - 2008-11-21 17:37:02
       
  
        
          
            Attachments:
            signature.asc
          
        
       
     | 
On Thu, 20 Nov 2008 12:51:12 +0200, 
Twylite <tw...@cr...> wrote:
The [finally script] usage is trivial to implement using unset traces
(although not quite as clean, mostly since it uses a magic variable
name).
  proc finally {script {varName --finally--trap--}} {
    upvar 1 $varName var
    trace add variable var unset [list apply [list args $script]]
  } ;# or something to that effect...
The [try] command for matching on something other than the return code
is excellent.  Especially if it can match on return values as well as
errorcodes.  How about this for a twist on the idea...
  try {
    script
  } catch {
    var ?opts?
  } then {
    script
  } handler .....and so on.....
It's a bit freaky, with one or two variable names being where a script
chunk should be, but solves the question of where to put the return
string and option variables.  Alternatively [try ?-vars ...? ....] or
something.
Regardless, why not have the handler clause evaluate an expression in
the context of a [dict with $opts]?  Then you can use whatever matching
function you wish, the only minor pain is that you have to use some
ugly bracketing of the option names { ${-code} == 2 }.  But maybe
there's a way around that, too, especially if the [dict with] is
doable read-only and non-destructively somehow.
And finally for over-all syntax, what'd be wrong with tagging the
try clauses onto the end of the present [catch] command.  Make the
options variable mandatory in this usage, and bring it into scope for
the evaluations as above.
Just one of my warped out there thoughts (assuming someone hasn't
suggested the very same thing on the next yet-to-be-read message...
And if they have, count this as a vote for that!  ;) ).
-- 
Fredderic
Debian/unstable (LC#384816) on i686 2.6.23-z2 2007 (up 44 days, 11:43)
 | 
| 
     
      
      
      From: Twylite <tw...@cr...> - 2008-11-19 10:43:05
       
   | 
Hi,
>> 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?
> Why are you using all of it at once? That's its most bulky case. (It's
> also something that's quite a PITA to write with just [catch], speaking
> as someone who's done that in the past...)
Fair point.  In most cases only a subset of the full functionality will 
be used.
OTOH I've had a stab at writing the synopsis line for the man page, and 
the bulkiness/complexity is not funny.
>> - The "except" clause needs to be flexible enough to handle the 
>> expected common use cases, but not so flexible/complex that it's 
>> easier to just use catch.  There is little clarity on what the 
>> "common use cases" are.
> So long as we can do multiple 'onerror' clauses and can omit the capture
> variables, we'll be OK. (The errorinfo and errorcode can be picked out
> of the options.) For the 'except' clause, it's probably only necessary
As currently specified you would use "onerror {glob ?emvar? ?optsvar?}", 
but if you omit emvar/optsvar then you have no access to this 
information within that handler.
> The other thing is that the various trap clauses should be arbitrarily 
> reorderable.
They are.  The syntax is "try /script/ ?handler? ?handler ...? ?finally 
body?" where handler is one of:
  then body
  onerror {glob ?emvar? ?optsvar?} body
  except {spec ?emvar? ?optsvar?} body
There can only be one 'then' handler.  Handlers are searched in order 
(left-to-right) until a matching one is found, and that one is 
executed.  Following handler executing the finally body (if any) is 
executed.  The 'finally' clause can be placed in any position where a 
handler is valid/expected (it doesn't have to be at the end).
>> - Often information about an error is implied in the result (as in 
>> Tcl_SetResult) rather than an errorCode -- can except (or some other 
>> clause) adequately handle this case?
> Wrong question. The right question is "should it handle the case?" and I
> think the answer is "no". Let's clean up the problems with critical info
> not going into the errorcode instead (and I know that might take a bit).
The problem with that approach is that it forces a workaround (via 
'catch' or an 'except' handler) when integrating with legacy 
code/components that are naughty.
>> A modification I find particularly interesting is an alternative 
>> approach to finally.
> I don't like that nearly as much, FWIW.
I'd be interested in reasons/insight?
Regards,
Twylite
 | 
| 
     
      
      
      From: Donal K. F. <don...@ma...> - 2008-11-19 11:01:25
       
   | 
Twylite wrote:
> OTOH I've had a stab at writing the synopsis line for the man page, and 
> the bulkiness/complexity is not funny.
Can't be helped. It's not as bad as [lsearch] or [canvas]...
> As currently specified you would use "onerror {glob ?emvar? ?optsvar?}", 
> but if you omit emvar/optsvar then you have no access to this 
> information within that handler.
Sounds acceptable to me. If people want the info, they'll capture it.
OTOH, they might be quite happy without. (For example, if trapping a
particular POSIX error from [open].) The only real question is whether
the msgvar and optvar should be that way round. Pass on that! :-)
>>> - Often information about an error is implied in the result (as in 
>>> Tcl_SetResult) rather than an errorCode -- can except (or some other 
>>> clause) adequately handle this case?
>> Wrong question. The right question is "should it handle the case?" and I
>> think the answer is "no". Let's clean up the problems with critical info
>> not going into the errorcode instead (and I know that might take a bit).
> The problem with that approach is that it forces a workaround (via 
> 'catch' or an 'except' handler) when integrating with legacy 
> code/components that are naughty.
Can't be helped. If one bends over backwards too far, one falls over and
fail to achieve one's goals. I think this is one of those times...
>>> A modification I find particularly interesting is an alternative 
>>> approach to finally.
>> I don't like that nearly as much, FWIW.
> I'd be interested in reasons/insight?
It doesn't feel right. Yeah, I know that's not the strongest of points,
but I think it's probably based on the fact that most users^Wprogrammers
are *very* confused by the whole idea of out-of-order execution. They
find [after] and [fileevent] difficult. [bind] too. :-\
Donal.
 | 
| 
     
      
      
      From: Joe E. <jen...@fl...> - 2008-11-19 19:01:13
       
   | 
Donal K. Fellows wrote:
> Twylite wrote:
> > [...]
> > As currently specified you would use "onerror {glob ?emvar? ?optsvar?}",
> > but if you omit emvar/optsvar then you have no access to this
> > information within that handler.
> Sounds acceptable to me. If people want the info, they'll capture it.
> OTOH, they might be quite happy without. [...]
> 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.
[ ... ]
> >>> A modification I find particularly interesting is an alternative
> >>> approach to finally.
> >> I don't like that nearly as much, FWIW.
> > I'd be interested in reasons/insight?
>
> It doesn't feel right. Yeah, I know that's not the strongest of points,
> but I think it's probably based on the fact that most users^Wprogrammers
> are *very* confused by the whole idea of out-of-order execution. They
> find [after] and [fileevent] difficult. [bind] too. :-\
I don't like the alternative approach much either,
simply because it smells too much like innovation.
try/finally as currently proposed is well-established
in other languages and is known to work well.  The
alternative approach certainly sounds interesting
and is probably worth pursuing, but *not for this TIP*.
I want to see this in 8.6.  Now is not the time to
invent brand new untested control flow constructs.
--Joe English
 | 
| 
     
      
      
      From: Neil M. <ne...@Cs...> - 2008-11-19 15:23:34
       
   | 
Twylite wrote:
> Hi,
>>>>    TIP #329	Try/Catch/Finally syntax
>>>     
>> I'd really like to see this in 8.6.  However, I also think
>> it's essential that try/catch be able to dispatch based on
>> the return code, not just $::errorCode, which the current
>> specification does not provide for.  Please fix!
>>   
> Although I haven't updated the spec, the current proposal is:
>   try {
>     #code
>   } then {
>     #success continuation
>   } onerror {glob emvar optsvar} {
>     #handle errors based on errorCode
>   } except {spec emvar optsvar} {
>     #handle errors based on any matching information in optsvar
>   } finally {
>     #always execute this as the last action before leaving the try command
>   }
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:
     try {
        ...
     } then {
        ...
     } else {
        ...
     } catch error {vars...} {
        ...
     } catch continue {...} {
        ...
     } catch -1200 {...} {
        ...
     } finally {
        ...
     }
(I'm happy to use "on" or "handle" instead of "catch").
The syntax of the catch part would be: catch "?exception-type? vars
script", where exception-type is a non-empty *list* of "error", 
"continue", "break", "return", "ok", or numeric return codes. It 
defaults to "error". The vars part is just "msgVar ?optsVar?". You must 
specify the vars even if not used (e.g. catch continue {} { .. }) to 
avoid ambiguity (an alternative would be to make the vars optional and 
the exception type mandatory). Note that in this scheme "then" is just 
sugar for "catch ok". "else" is a catch-all, and is invoked if no other 
catch block matches the return code.
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. You might think that is unfortunate, and that it
would be better if more people made use of errorCode, but that's the way
it is. 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:
     try {
        ...
     } catch error {msg opts} {
        switch -glob [dict get $opts -errorcode] {
            {FOO *} { ... }
            default { ... }
        }
     } finally { ... }
To me, this strikes a good balance between flexibility and simplicity.
In particular, it leaves pattern matching to existing commands [switch],
[regexp] and so on. So, for instance we won't down the line have people
asking for catch -regexp or catch -nocase and so on (which would almost
certainly happen if errorCode usage does take on).
Off the top of my head, the following is a sketch implementation (only 
partially tested):
proc try {script args} {
     set finally [from args finally ""]
     set else [from args else ""]
     set then [from args then ""]
     set handlers [dict create 0 [list _ $then]]
     foreach h [lsplit $args "catch"] {
         if {[llength $h] ni {2 3}} { error "wrong # args: $h" }
         if {[llength $h] == 2} { set h [linsert $h 0 error] }
         lassign $h types vars body
         foreach type $types {
             dict set handlers [returncode $type] [list $vars $body]
         }
     }
     set rc [catch { uplevel 1 $script } msg opts]
     if {[dict exists $handlers $rc]} {
         lassign [dict get $handlers $rc] vars body
         lassign $vars msgVar optsVar
         upvar 1 $msgVar cmsg $optsVar copts
         set cmsg $msg; set copts $opts
         catch { uplevel 1 $body } msg opts
     } elseif {$else ne ""} {
         if {[catch { uplevel 1 $else } emsg eopts]} {
             set msg $emsg; set opts $eopts
         }
     }
     uplevel 1 $finally
     dict incr opts -level
     return -options $opts $msg
}
(Utility procs at end of this message [*]).
This allows use-cases like:
# Ensure that a channel is closed after use:
# using fd [open $myfile] { puts [read $myfile] }
proc using {varName chan body} {
     upvar 1 $varName var
     set var $chan
     try { uplevel 1 $body } finally { close $chan }
}
proc readfile file { using c [open $file] { return [read $c] } }
# Sugar for db transactions
proc transaction {db body} {
     $db start transaction
     try {
         uplevel 1 $body
     } catch {ok continue return} {msg opts} {
         $db commit
         return -options $opts $msg
     } else {
         $db abort
     }
}
# Version of [dict with] that only updates the dict on success
proc atomic-with {dictVar body} {
     upvar 1 $dictVar dict
     set env $dict
     try { dict with env $body } then { set dict $env }
}
# Mutex locking
proc lock {mutex script} {
     thread::mutex lock $mutex
     try { uplevel 1 $script } finally { thread::mutex unlock $mutex }
}
# Loop through each line of a file
proc foreach-line {lineVar file body} {
     upvar 1 $lineVar line
     using chan [open $file] {
         while {[gets $chan line] >= 0} {
             try { uplevel 1 $body } catch msg { puts "ERROR: $msg" }
         }
     }
}
Note that these do the Right Thing with regard to most exceptions. In 
particular, note that [try] is transparent wrt exceptions it doesn't 
catch -- i.e. [try { $script }] is equivalent to $script. For example, 
the foreach-line proc only catches errors, so break/continue are 
propagated as normal:
  foreach-line line foo.txt {
      if {[incr i] % 2 == 0} { continue }
      puts $line
  }
To me, these are the compelling use-cases for a try construct. errorCode 
matching seems less generally useful.
----
[*] Utility procs:
proc from {listVar option {default ""}} {
     upvar 1 $listVar list
     set idx [lsearch -exact $list $option]
     if {$idx >= 0} {
         set default [lindex $list [expr {$idx+1}]]
         set list [lreplace $list $idx [incr idx]]
     }
     return $default
}
proc lsplit {xs delim} {
     set ys [list]
     set y [list]
     foreach x [lappend xs $delim] {
         if {$x eq $delim} {
             if {[llength $y]} { lappend ys $y; set y [list] }
         } else { lappend y $x }
     }
     return $ys
}
proc returncode code {
     if {[string is integer -strict $code]} { return $code }
     return [lsearch -exact {ok error return break continue} $code]
}
-- Neil
This message has been checked for viruses but the contents of an attachment
may still contain software viruses, which could damage your computer system:
you are advised to perform your own checks. Email communications with the
University of Nottingham may be monitored as permitted by UK legislation.
 | 
| 
     
      
      
      From: Donal K. F. <don...@ma...> - 2008-11-19 16:39:06
       
   | 
Neil Madden wrote:
> 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'm going to disagree with you...
>      } else {
>         ...
>      } catch error {vars...} {
>         ...
>      } catch continue {...} {
>         ...
>      } catch -1200 {...} {
>         ...
Those 'catch' clauses cause me problems. You seem to me to be wanting to
make a single clause for catching all errors, but to me one of the main
points about [try] is that it gets away from that. It's a main point
because it makes a real difference for elevating code above the level of
the current [catch] capabilities. Right now, doing code to handle
different errors with [catch] is fairly ugly (though easier than before
TIP#90) and your proposal doesn't help that much. Twylite's proposal is
more workable, as it matches errors against the errorcode and everything
else against the result code (I suppose we could allow a * for anything
in the spec part of the 'except' matcher, but it's not clear to me why
you'd want to).
> (I'm happy to use "on" or "handle" instead of "catch").
That's more of a bikeshed issue.
> 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. You might think that is unfortunate, and that it
> would be better if more people made use of errorCode, but that's the way
> it is.
That's why 'onerror' and 'except' clauses are both needed. (The names
could be better, but arguing syntax when the semantics still need fixing
is wasteful.)
> 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:
No. That's advocating a lack of vision. If we make it *easy* to trap
specific errors by errcode, people will do so. People don't now because
it is too hard.
> To me, this strikes a good balance between flexibility and simplicity.
> In particular, it leaves pattern matching to existing commands [switch],
> [regexp] and so on. So, for instance we won't down the line have people
> asking for catch -regexp or catch -nocase and so on (which would almost
> certainly happen if errorCode usage does take on).
What is proposed can still permit such things with an enclosed [switch]
as they can still match all errorcodes with *, but it should encourage
people to make better use of what we've got.
Donal.
 | 
| 
     
      
      
      From: Neil M. <ne...@Cs...> - 2008-11-19 17:30:52
       
   | 
On 19 Nov 2008, at 16:38, Donal K. Fellows wrote:
> Neil Madden wrote:
>> 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'm going to disagree with you...
>
>>      } else {
>>         ...
>>      } catch error {vars...} {
>>         ...
>>      } catch continue {...} {
>>         ...
>>      } catch -1200 {...} {
>>         ...
>
> Those 'catch' clauses cause me problems. You seem to me to be  
> wanting to
> make a single clause for catching all errors, but to me one of the  
> main
> points about [try] is that it gets away from that. It's a main point
> because it makes a real difference for elevating code above the  
> level of
> the current [catch] capabilities. Right now, doing code to handle
> different errors with [catch] is fairly ugly (though easier than  
> before
> TIP#90) and your proposal doesn't help that much. Twylite's  
> proposal is
> more workable, as it matches errors against the errorcode and  
> everything
> else against the result code (I suppose we could allow a * for  
> anything
> in the spec part of the 'except' matcher, but it's not clear to me why
> you'd want to).
Show me the use-cases! Handling different error-cases with [catch] is  
not that ugly now:
     catch { $script } msg opts
     switch -glob [dict get $opts -errorcode] {
         ... { ... }
         ... { ... }
         default { return -options $opts $msg }
     }
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.
>
>> (I'm happy to use "on" or "handle" instead of "catch").
>
> That's more of a bikeshed issue.
>
>> 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. You might think that is unfortunate, and that it
>> would be better if more people made use of errorCode, but that's  
>> the way
>> it is.
>
> That's why 'onerror' and 'except' clauses are both needed. (The names
> could be better, but arguing syntax when the semantics still need  
> fixing
> is wasteful.)
>
>> 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:
>
> No. That's advocating a lack of vision. If we make it *easy* to trap
> specific errors by errcode, people will do so. People don't now  
> because
> it is too hard.
I just don't see any evidence for this statement (the use-cases will  
follow if we just implement this!).
>
>> To me, this strikes a good balance between flexibility and  
>> simplicity.
>> In particular, it leaves pattern matching to existing commands  
>> [switch],
>> [regexp] and so on. So, for instance we won't down the line have  
>> people
>> asking for catch -regexp or catch -nocase and so on (which would  
>> almost
>> certainly happen if errorCode usage does take on).
>
> What is proposed can still permit such things with an enclosed  
> [switch]
> as they can still match all errorcodes with *, but it should encourage
> people to make better use of what we've got.
-- Neil
This message has been checked for viruses but the contents of an attachment
may still contain software viruses, which could damage your computer system:
you are advised to perform your own checks. Email communications with the
University of Nottingham may be monitored as permitted by UK legislation.
 | 
| 
     
      
      
      From: Joe E. <jen...@fl...> - 2008-11-19 19:24:32
       
   | 
Neil Madden wrote: > Donal K. Fellows wrote: > > Neil Madden wrote: > >> I'd leave out entirely the glob-matching on errorCode, and let > >> people do > >> this with [switch] if they want it: > > No. That's advocating a lack of vision. If we make it *easy* to trap > > specific errors by errcode, people will do so. People don't now > > because > > it is too hard. > > I just don't see any evidence for this statement (the use-cases will > follow if we just implement this!). 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. (FWIW, I don't think try/onerror will be immediately useful either, and am not sure that it will _ever_ be useful -- perhaps meaningful -errorcodes wouldn't be that helpful even if we did have them -- but it's worth a try.) --Joe English [*] and by "people" I mean "including core maintainers", and by "including core maintainers" I mean "especially".  | 
| 
     
      
      
      From: <lm...@bi...> - 2008-11-19 21:27:11
       
   | 
On Wed, Nov 19, 2008 at 11:24:26AM -0800, Joe English wrote: > > Neil Madden wrote: > > Donal K. Fellows wrote: > > > Neil Madden wrote: > > >> I'd leave out entirely the glob-matching on errorCode, and let > > >> people do > > >> this with [switch] if they want it: > > > No. That's advocating a lack of vision. If we make it *easy* to trap > > > specific errors by errcode, people will do so. People don't now > > > because > > > it is too hard. > > > > I just don't see any evidence for this statement (the use-cases will > > follow if we just implement this!). > > 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. Yeah, heaven forbid that there be a concept of NULL in tcl that could be trivially used to indicate EOF/ERROR/whatever. Much better to introduce language constructs (that will probably get used about as much as -errorcode). -- --- Larry McVoy lm at bitmover.com http://www.bitkeeper.com  | 
| 
     
      
      
      From: Twylite <tw...@cr...> - 2008-11-20 16:40:13
       
   | 
Awesome stuff Neil - thanks for the detailed analysis :)
>> 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.
> I believe this is already addressed by the presence of errorCode. 
> Pattern-matching against this code is already quite simple, with 
> [switch] and so on. It just hasn't caught on. It seems like wishful 
> thinking to expect that it will suddenly catch on just because its 
> usage is made slightly more convenient (we're talking about a 
> reduction of about 1 line of code).
As you note, it hasn't caught on.  I believe that is because (i) the 
language feature to throw errors discourages (or at least does not 
encourage) the use of errorCode, and (ii) there is no obvious feature to 
branch based on errorCode.  I intend to resolve that through this TIP by 
making [try] support branching on errorCode, and [throw] reorder the 
arguments of [error] to encourage the use of an errorCode.
> I think it is worth explicitly pulling out the separate concerns 
> embodied in this TIP and tying them to use-cases. As I see it, there 
> are a number of more-or-less separable concerns here:
>
> 1. Support for ensuring resource cleanup in the presence of 
> exceptions/errors (i.e. "finally").
> 2. Better support for case-analysis based on exception return code.
> 3. Better support for case-analysis based on errorcode.
> 4. Ability to trap only those exceptions/errors that you are 
> interested in/prepared to deal with, letting others propagate normally.
Most wicked - I had just made this list myself (well, the first 3 at 
least).  We have 3 orthogonal concerns here that we are trying to 
shoehorn into one command.
> try { process $chan } finally { close $chan }
>
> This is a saving of two lines, but I think the improvement in 
> readability is worth it. A possible improvement would be for any 
> errors in the finally clause to not completely mask any errors in the 
> main script -- perhaps by adding a -original field to the options dict 
> (which itself contains the message and options dict of the original 
> error).
>
> I'd be happy to see this part of the TIP dropped or separated into a 
> different command however.
I wouldn't be particularly happy to drop/separate this functionality.  
Doing so will result in more deeply nested code, because the need for 
cleanup regularly interacts with the need to handle (some) 
errors/exceptions (rather than let them all propagate).
Also, there are three orthogonal concerns, all wanting to be called 
"try", which will just get messy.
My proposal (or at least implementation, I forget) chains errors to the 
original in all cases (i.e. if you throw an error from a handler or from 
finally).
> 2. Case-analysis based on return code
> An example here is that of implementing custom control structures. Of 
> particular interest here is the use of standard exceptions like 
> [break] and [continue]. For instance, we may want to write a version 
> of [foreach] that operates asynchronously using the event loop. 
> Currently, we might write this as:
> The try/handle (Twylite/JE) alternative would be:
>
>      } handle {code msg opts} {
>          switch $code {
>              3       { return }
>              0 - 4   { # ok/continue }
>              default { return -options $opts $msg }
>          }
>      }
Umm ... no. JE said:
> 	} handle {code ?resultVar ?optionsVar??} {
> 	    #
> 	    # return code was $code.
> 	    #
> 	} finally {
>   
> where "code" is one of ok/error/return/break/continue
> or an integer literal, a la [return -code].
>   
So the use would be
  } handle {2 msg opts} {
    # handle the case where the return code is 2 (TCL_RETURN)
  } handle {1 msg opts} {
    # handle the case where the return code is 1 (TCL_ERROR)
  }
etc.  And you can use the keywords ok/error/return/break/continue 
instead of the integer literals.
On http://wiki.tcl.tk/21608 I proposed using the keyword 'except' and 
matching on 'spec' which I hadn't defined, but was tentatively assuming 
to be the return code.
In other words, our proposal is the same as the try/catch example you 
present.
>  proc async-foreach {varName list body} {
>      if {[llength $list] == 0} { return }
>      set list [lassign $list item]
>      try {
>          apply [list $varName $body] $item
>      } catch break {} { return } catch continue {} {}
>      after 0 [list async-foreach $varName $list $body]
>  }
The difference is that you use
  catch on_what_code {?emvar? ?optsvar?} ?body?
and we use
  handle {on_what_code ?emvar? ?optsvar?} ?body?
> Note that dispatch based on exception code is a simple branch. There 
> is no need for complex pattern matching, sub-match capture, or 
> case-insensitive matching.
Mmm ... except you cheat in your example ;)
In the examples using [switch] you use a range (0 - 4), which try/catch 
doesn't handle.    And you put all the cases on one line ;p
IF a catch/handle can only match on exactly one return code, the 
branching is simple.  But you may want to match on a range.
Or even against a discrete list:
> The syntax of the catch part would be: catch "?exception-type? vars
> script", where exception-type is a non-empty *list* of "error", 
> "continue", "break", "return", "ok", or numeric return codes. It 
> defaults to "error".
> 3. Case-analysis based on errorcode
>
> For this, I'll use Twylite's example of trying different 
> authentication schemes. We will assume that the API throws an error 
> with code BADAUTH when the wrong scheme is used, and throws other 
> errors such as NOCONN to indicate that a connection to the host 
> failed. (Note: this example doesn't require glob-matching, but I don't 
> think the changes in such a case are that great). The existing way to 
> handle this would be:
man tclVars: " This list value represents additional information about 
the error in a form that is easy to process with programs. The first 
element of the list identifies a general class of errors, and determines 
the format of the rest of the list."
You need to do pattern matching on errorCode, or select based on [lindex 
$::errorCode 0] assuming that only the most general class information is 
relevant.
> OK, in this case the try/onerror approach certainly is clearer, and 
> the try/catch approach is little better than the existing way with 
> [catch] alone. I think it still wins slightly over [catch] in 
> readability. While it is more verbose, the control flow is easier to 
> read -- the [return] is in an obvious place, and not tucked away in an 
> inconspicuous "else" clause. There's also less punctuation. But the 
> onerror approach is clearly better for this case.
Forgive me for leaving our the preceding 40 lines of example, and 
focusing on this paragraph ;)
> The questions then, are whether "onerror" is sufficient for all/most 
> such cases, and whether these cases actually arise often/ever in 
> practice (or would arise given appropriate promotion). Regarding the 
> first part, dispatching based on errorcode is more complex than based 
> on return code as while the latter is a simple integer, the former can 
> be an arbitrarily complex data structure. In particular, the following 
> requirements may have to be considered:
>
>  a. Different forms of pattern-matching (e.g. exact, glob, regexp, 
> "algebraic" type matching etc). If we stick to one type only, will 
> that be appropriate? Will it cause problems? (e.g. if glob-only 
> matching, then we have problems specifying glob-special characters 
> such as * or []).
>  b. Case sensitivity -- is "arith" the same error as "ARITH" or "ARiTh"?
>  c. Sub-match capture: an errorcode may contain detail fields which we 
> want to extract. It seems pointless to match once and then perform a 
> separate extraction when I could have just used [regexp] or some other 
> matching facility and performed both operations in one go.
>  d. Disjunctive matching: perform this action if the errorcode matches 
> either *this* or *that*.
>
> I'm sure there are others. To me, the range of choices here suggests 
> that pattern matching is best kept separate from error-handling. 
> Otherwise there is a risk of duplicating [switch].
It seems to me that the [try] must not dictate/limit the matching, but 
must facilitate it.
The problem with catch+switch and similar approaches is that they are 
_ugly_.  Your try/catch suggestion is no more powerful than 
catch+switch, but it is more readable.  When you scan the code you see 
"try ... catch", and the intention of the code is clear.  When you see 
"catch ... switch" the intention is not clear -- you have no idea that 
these two statements are related, and if they are then on what basis are 
you switching, etc.
If one accepts that Tcl return codes should be used for flow control, 
and that the Tcl return code 1 (TCL_ERROR) covers all errors (which are 
called structured exceptions in languages like C++ and Java), and that 
there is a need to distinguish between different structured exceptions 
(Tcl errors) based on their cause/type/class/nature (whatever you want 
to call it), then the inescapable conclusion is that there needs to be a 
not-ugly control structure to handle branching based on return code, and 
a not-ugly control structure to handle branching based on error cause.  
They may or may not be the same control structure, but they are both 
equally necessary.
> Perhaps I am wrong here, and glob-matching meets all requirements.
Maybe, maybe not.  I'm honestly not sure either way.  Assuming that 
errorCode is constructed in a hierarchical manner (most general class 
first, becoming progressively more specific) then I can't think of a 
situation that a glob match can't handle (where an equivalent situation 
exists in say Java or C++ that can be handled by their syntax).
Of course its entirely possible that some bright spark declares that if 
the general class of errorCode is "OBJECT" then [lindex $errorCode 1] is 
an oo::object that is a child of tcl::errorobj, and you want to do 
class-based matching on said object.  This is probably quite a strong 
argument against glob matching (as the only option).
> Personally, if errorcode matching was to take off, I would use some 
> form of algebraic types (tagged lists) both for constructing and 
> pattern-matching errorcodes, as that seems to me to be the most 
> appropriate tool for the job. Perhaps there is a way to keep the 
> behaviour but to parameterise the matching command (with switch 
> -glob/string match being the default).
Yes, there is, maybe.  More below.
> The other question is whether these cases arise in practice. I can't 
> think of a single existing API that requires this kind of errorcode 
> pattern matching. Is such a design even appropriate? Clearly, if you 
> controlled the authentication scheme interface then you could just 
> return continue for BADAUTH and an error for anything else:
Depending on your understanding of "pattern matching" ... Java.  Like 
the whole Java Runtime Library.
Java obviously doesn't use globs to match exceptions, but it does throw 
different exceptions and you can catch a group of exceptions based on a 
pattern (specifically: the exception object is of a particular class).
errorCode is defined as being a list where the first element identifies 
the general class of errors; assuming that this format is maintained 
(for legacy compatibility) the most obvious approach to asking "is this 
an IO error" is "IO *".
So I have been suggesting "string match" on a structured list as an 
approximation of the 'isa' / 'typeof' operator.
My point here applies most specifically to cases where you don't control 
the API (which is rather common), but also to cases where you don't want 
to modify the API - because it will affect other working code that you 
don't want to refactor, or because you believe that adding a flow 
control statement like 'break' or 'continue' outside of a flow control 
construct is an inherently dangerous code practice because developers 
using APIs don't expect stuff like that.
>   4. Trapping only those errors/exceptions you are interested in
> It's clear from the above that the Twylite/JE approach achieves this 
> for errors, but not for other exceptions. My approach achieves it for 
> exceptions but not for more specific error cases.
I disagree -- is this statement based on a misunderstanding of what 
'except' / 'handle' was doing?
> I'd really prefer things to be unified in name at least: "on error", 
> "on break", etc. One possible unification that might please all would 
> be to adopt the syntax "on exception-type ?pattern? ?vars? body". The 
> pattern is an optional glob-style pattern that is matched against the 
> -errorcode of the exception. (If the pattern is specified then so must 
> the vars). Clearly this is mostly useful in the case of errors, but I 
> believe it is possible for non-error exceptions to also set 
> -errorcode, so it might be useful elsewhere. That would result in the 
> following use-cases:
Anything can set return options (including -errorcode) by using 
[return], but only a code 1 return ( [error] ) automatically adds the 
-errorcode to the dict.
>   proc connect {schemes host user pass} {
>      foreach scheme $schemes {
>          try {
>              return [$scheme connect $host $user $pass]
>          } on error BADAUTH {} { continue }
>      }
>      error "unable to authenticate"
>  }
> To me this seems like a good compromise, and people who want more 
> complex pattern matching can still do so. I'd like to be able to 
> support lists of exception types. I still don't believe glob-style 
> errorCode pattern matching is useful or particularly satisfactory, but 
> I'm willing to concede it for the sake of compromise. As before, 
> define "then" as "on ok", and possibly define "else/otherwise" as a 
> catch-all default clause.
It looks promising, but I doubt that errorCode is every really 
meaningful outside the context of TCL_ERROR, and I'm leaning towards 
glob being insufficient as the only supported matching style.  It's all 
your fault for being right ;p
> <aside>
> I also see this meshing nicely with a hypothetical future 
> continuation-based exception mechanism that allows 
> resumable/non-destructive exception/warning notifications.
> </aside>
I've been glimpsing some opportunities here, but I don't work with CPS 
enough to have a good idea of how to work it in.
>> 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).
> Branching based on the result just seems like a fragile nightmare. 
> Localised error messages for instance could totally change the 
> behaviour of code.
Oh, it is ;(  But its also the only option available in some existing 
libraries.  I've encountered code that puts an error identified in the 
result and the error message in errorInfo, for example.
The question then is: how widespread is such (bad) code.  Would 
developers regularly resort to another branching mechanism (try+switch, 
catch+switch, etc.) or would they be able to use the new [try] for the 
vast majority of their code?
>> 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.
> Hmmm... error handling needs case-analysis not typing per se. Clearly, 
> any mechanism used for constructing error cases is strongly related to 
> the mechanism that is used for distinguishing them, which is why I'd 
> rather see these aspects separated/parameterised from the exception 
> handling, at least until it is clear what the best mechanism for this is.
I'm going to take a stab at identifying what the best mechanism is.
Let's move back for a moment to what we're trying to achieve here.
Functionality:
  a. Handle errors by class (errorCode)
  b. Handle success continuation (i.e. don't limit the control structure 
to only exceptional situations)
  c. Handle other (non-error) exceptions
  d. It is clear that (a), (b) and (c) are all specialisations of 
matching the Tcl return code (and possibly some other return 
information, like errorCode and result)
  e. Clean up at the end of an operation, regardless of errors/exceptions
Look & Feel:
  a. Looks like if/then/else (traditional structured try/catch as in 
C++, Java, etc), OR
  b. Looks like switch (I don't actually know of another language that 
does this), OR
  c. Hybrid of (a) and (b): choose between success/error with an 
if/then/else like construct, then switch on the specific result/error 
(e.g. Erlang)
Matching:
  a. Match a single return code, a range of return codes, or a discrete 
list of return codes
  b. Match a specific error code, and an errorcode pattern (glob-like)
  c. Possibly provide for more complex matches involving other fields 
(result), regular expressions, disjunctive matching, case sensitivity, 
relational calculus/algebra, whatever.
catch+something_else can provide for arbitrary complexity.  Should [try] 
also do so, or should its complexity be limited?  If limited, what is a 
reasonable limit?  Exact match?  Range match?  Glob match?
It strikes me that there are two possible solutions:
(1) Make [try] an unholy union of [catch] and [switch] such that the 
user can specify the information being switched on (e.g. "%C,%E,%R" 
where %C is the return code, %E is the errorcode and %R is the result).  
You delegate pattern matching to [switch] and allow the user to match 
based on any combination of return code, errorcode and result.
  try {
    # ...
  } thenwith -glob -- "%C,%E" {
    "1,POSIX *" { handle posix errors }
    "3,*" -
    "4,*" { handle break/continue }
  }
(2) Make [try] an unholy union of [catch] and [if]/then/else, and 
provide helper functions/operations to match exception/error cases with 
expr.
  try {
    # ...
  } handle { [string match "POSIX *" $::errorCode] } {
    handle posix errors
  } handle { code() in {3 4} } {
    handle continue
  }
Asssuming some minor changes to expr that could look more like:
  try {
    # ...
  } handle { errorcode() like "POSIX *" } {
  } handle { code() in {3 4} } {
  }
I found the question on Reddit at 
http://www.reddit.com/r/programming/comments/7dgwy/ask_proggit_where_clauses_in_catch_declarations/?sort=old 
quite interesting in this regard.
Ick ... time to get home.
Twylite
 | 
| 
     
      
      
      From: Neil M. <ne...@Cs...> - 2008-11-20 19:11:24
       
   | 
On 20 Nov 2008, at 16:39, Twylite wrote:
>> [...]
[on finally:]
>> I'd be happy to see this part of the TIP dropped or separated into a
>> different command however.
> I wouldn't be particularly happy to drop/separate this functionality.
My thought here was merely that a decision on "finally" could be  
separated from considerations of try/catch. I.e., it could form a  
separate TIP, either extending [try] or adding a separate capability  
like your [finally] command or some [ensure] command or something.  
Just an idea though -- I think "finally" as part of try is a well  
established convention and fairly uncontroversial.
> Doing so will result in more deeply nested code, because the need for
> cleanup regularly interacts with the need to handle (some)
> errors/exceptions (rather than let them all propagate).
> Also, there are three orthogonal concerns, all wanting to be called
> "try", which will just get messy.
>
> My proposal (or at least implementation, I forget) chains errors to  
> the
> original in all cases (i.e. if you throw an error from a handler or  
> from
> finally).
OK, good.
>> 2. Case-analysis based on return code
>> An example here is that of implementing custom control structures. Of
>> particular interest here is the use of standard exceptions like
>> [break] and [continue]. For instance, we may want to write a version
>> of [foreach] that operates asynchronously using the event loop.
>> Currently, we might write this as:
>> The try/handle (Twylite/JE) alternative would be:
>>
>>      } handle {code msg opts} {
>>          switch $code {
>>              3       { return }
>>              0 - 4   { # ok/continue }
>>              default { return -options $opts $msg }
>>          }
>>      }
> Umm ... no. JE said:
>> 	} handle {code ?resultVar ?optionsVar??} {
>> 	    #
>> 	    # return code was $code.
>> 	    #
>> 	} finally {
>>
>> where "code" is one of ok/error/return/break/continue
>> or an integer literal, a la [return -code].
>>
> So the use would be
>   } handle {2 msg opts} {
>     # handle the case where the return code is 2 (TCL_RETURN)
>   } handle {1 msg opts} {
>     # handle the case where the return code is 1 (TCL_ERROR)
>   }
> etc.  And you can use the keywords ok/error/return/break/continue
> instead of the integer literals.
OK. The comment "return code was $code" made me think that "code" was  
a variable, rather than a pattern.
>
> On http://wiki.tcl.tk/21608 I proposed using the keyword 'except' and
> matching on 'spec' which I hadn't defined, but was tentatively  
> assuming
> to be the return code.
>
> In other words, our proposal is the same as the try/catch example you
> present.
Great. In that case, I think we're largely in agreement.
>>  proc async-foreach {varName list body} {
>>      if {[llength $list] == 0} { return }
>>      set list [lassign $list item]
>>      try {
>>          apply [list $varName $body] $item
>>      } catch break {} { return } catch continue {} {}
>>      after 0 [list async-foreach $varName $list $body]
>>  }
> The difference is that you use
>   catch on_what_code {?emvar? ?optsvar?} ?body?
> and we use
>   handle {on_what_code ?emvar? ?optsvar?} ?body?
>> Note that dispatch based on exception code is a simple branch. There
>> is no need for complex pattern matching, sub-match capture, or
>> case-insensitive matching.
> Mmm ... except you cheat in your example ;)
> In the examples using [switch] you use a range (0 - 4), which try/ 
> catch
> doesn't handle.    And you put all the cases on one line ;p
It's not a range - the "-" in switch means "use the same body as the  
next branch". i.e., the example means codes 0 and 4 (ok and  
continue), not 0 to 4.
> [...]
> It seems to me that the [try] must not dictate/limit the matching, but
> must facilitate it.
> The problem with catch+switch and similar approaches is that they are
> _ugly_.  Your try/catch suggestion is no more powerful than
> catch+switch, but it is more readable.  When you scan the code you see
> "try ... catch", and the intention of the code is clear.  When you see
> "catch ... switch" the intention is not clear -- you have no idea that
> these two statements are related, and if they are then on what  
> basis are
> you switching, etc.
>
> If one accepts that Tcl return codes should be used for flow control,
> and that the Tcl return code 1 (TCL_ERROR) covers all errors (which  
> are
> called structured exceptions in languages like C++ and Java), and that
> there is a need to distinguish between different structured exceptions
> (Tcl errors) based on their cause/type/class/nature (whatever you want
> to call it), then the inescapable conclusion is that there needs to  
> be a
> not-ugly control structure to handle branching based on return  
> code, and
> a not-ugly control structure to handle branching based on error cause.
> They may or may not be the same control structure, but they are both
> equally necessary.
OK. On reflection I'm willing to concede that adequate error case  
analysis requires pattern matching of some sort built-in to [try].
>> Perhaps I am wrong here, and glob-matching meets all requirements.
> Maybe, maybe not.  I'm honestly not sure either way.  Assuming that
> errorCode is constructed in a hierarchical manner (most general class
> first, becoming progressively more specific) then I can't think of a
> situation that a glob match can't handle (where an equivalent  
> situation
> exists in say Java or C++ that can be handled by their syntax).
> Of course its entirely possible that some bright spark declares  
> that if
> the general class of errorCode is "OBJECT" then [lindex $errorCode  
> 1] is
> an oo::object that is a child of tcl::errorobj, and you want to do
> class-based matching on said object.  This is probably quite a strong
> argument against glob matching (as the only option).
Good point. I hadn't thought of that. That also could handle subtype- 
based matching.
>> Personally, if errorcode matching was to take off, I would use some
>> form of algebraic types (tagged lists) both for constructing and
>> pattern-matching errorcodes, as that seems to me to be the most
>> appropriate tool for the job. Perhaps there is a way to keep the
>> behaviour but to parameterise the matching command (with switch
>> -glob/string match being the default).
> Yes, there is, maybe.  More below.
>> The other question is whether these cases arise in practice. I can't
>> think of a single existing API that requires this kind of errorcode
>> pattern matching. Is such a design even appropriate? Clearly, if you
>> controlled the authentication scheme interface then you could just
>> return continue for BADAUTH and an error for anything else:
> Depending on your understanding of "pattern matching" ... Java.  Like
> the whole Java Runtime Library.
> Java obviously doesn't use globs to match exceptions, but it does  
> throw
> different exceptions and you can catch a group of exceptions based  
> on a
> pattern (specifically: the exception object is of a particular class).
> errorCode is defined as being a list where the first element  
> identifies
> the general class of errors; assuming that this format is maintained
> (for legacy compatibility) the most obvious approach to asking "is  
> this
> an IO error" is "IO *".
> So I have been suggesting "string match" on a structured list as an
> approximation of the 'isa' / 'typeof' operator.
Which could be workable: the OO exception just dumps [$self info  
ancestors] or whatever into the errorCode, so you have something  
like: [list Error IOError HostUnreachable object12].
> My point here applies most specifically to cases where you don't  
> control
> the API (which is rather common), but also to cases where you don't  
> want
> to modify the API - because it will affect other working code that you
> don't want to refactor, or because you believe that adding a flow
> control statement like 'break' or 'continue' outside of a flow control
> construct is an inherently dangerous code practice because developers
> using APIs don't expect stuff like that.
Well, all exceptions affect flow control.
> [...]
>>   proc connect {schemes host user pass} {
>>      foreach scheme $schemes {
>>          try {
>>              return [$scheme connect $host $user $pass]
>>          } on error BADAUTH {} { continue }
>>      }
>>      error "unable to authenticate"
>>  }
>> To me this seems like a good compromise, and people who want more
>> complex pattern matching can still do so. I'd like to be able to
>> support lists of exception types. I still don't believe glob-style
>> errorCode pattern matching is useful or particularly satisfactory,  
>> but
>> I'm willing to concede it for the sake of compromise. As before,
>> define "then" as "on ok", and possibly define "else/otherwise" as a
>> catch-all default clause.
> It looks promising, but I doubt that errorCode is every really
> meaningful outside the context of TCL_ERROR, and I'm leaning towards
> glob being insufficient as the only supported matching style.  It's  
> all
> your fault for being right ;p
:-) Sorry!
I agree that errorcode is pretty unlikely to be useful outside of  
errors, hence making it optional. I still quite like the symmetry of  
this proposal.
>> <aside>
>> I also see this meshing nicely with a hypothetical future
>> continuation-based exception mechanism that allows
>> resumable/non-destructive exception/warning notifications.
>> </aside>
> I've been glimpsing some opportunities here, but I don't work with CPS
> enough to have a good idea of how to work it in.
It's a nice vision, but not workable within a 8.x timeframe. To  
really work, it would require changing the way C code reports  
exceptions (e.g., calling some Tcl_Throw rather than returning an  
error code). With the NRE changes though, this kind of thing begins  
to look more feasible though.
>>> 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).
>> Branching based on the result just seems like a fragile nightmare.
>> Localised error messages for instance could totally change the
>> behaviour of code.
> Oh, it is ;(  But its also the only option available in some existing
> libraries.  I've encountered code that puts an error identified in the
> result and the error message in errorInfo, for example.
> The question then is: how widespread is such (bad) code.  Would
> developers regularly resort to another branching mechanism (try 
> +switch,
> catch+switch, etc.) or would they be able to use the new [try] for the
> vast majority of their code?
I think in this case this is a common idiom we want to actively  
discourage! :-)
>>> 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.
>> Hmmm... error handling needs case-analysis not typing per se.  
>> Clearly,
>> any mechanism used for constructing error cases is strongly  
>> related to
>> the mechanism that is used for distinguishing them, which is why I'd
>> rather see these aspects separated/parameterised from the exception
>> handling, at least until it is clear what the best mechanism for  
>> this is.
> I'm going to take a stab at identifying what the best mechanism is.
>
> Let's move back for a moment to what we're trying to achieve here.
>
> Functionality:
>   a. Handle errors by class (errorCode)
>   b. Handle success continuation (i.e. don't limit the control  
> structure
> to only exceptional situations)
>   c. Handle other (non-error) exceptions
>   d. It is clear that (a), (b) and (c) are all specialisations of
> matching the Tcl return code (and possibly some other return
> information, like errorCode and result)
>   e. Clean up at the end of an operation, regardless of errors/ 
> exceptions
>
> Look & Feel:
>   a. Looks like if/then/else (traditional structured try/catch as in
> C++, Java, etc), OR
>   b. Looks like switch (I don't actually know of another language that
> does this), OR
>   c. Hybrid of (a) and (b): choose between success/error with an
> if/then/else like construct, then switch on the specific result/error
> (e.g. Erlang)
>
> Matching:
>   a. Match a single return code, a range of return codes, or a  
> discrete
> list of return codes
>   b. Match a specific error code, and an errorcode pattern (glob-like)
>   c. Possibly provide for more complex matches involving other fields
> (result), regular expressions, disjunctive matching, case sensitivity,
> relational calculus/algebra, whatever.
Ha! Maybe subsumption based matching using a description logic  
reasoner? :-)
It seems clear that matching on a the return code (or a list of) can  
be done in O(1). It seems we agree that this can and should be  
supported. How much to support narrowing down beyond that is the main  
focus of debate. I'm prepared to agree with you that some sort of  
errorCode matching would be beneficial. Glob-matching errorCode is  
reasonably cheap and probably does cover most common cases. Another  
cheap alternative would be to treat the pattern as simply a list of  
constants and then match by finding longest common prefix between the  
matches and the errorcode, which would have worst case O(N) where N  
is the llength of $errorCode. That also has the advantage that you  
can compile all catch blocks into a single pattern matching tree  
(trie-like). While customised pattern matching is appealing, it seems  
likely to involve extra complexity in both usage and implementation,  
with probably performance impacts too. User-supplied predicates or  
customisable pattern matching is likely to be much less efficient, so  
should probably not be the default.
>
> catch+something_else can provide for arbitrary complexity.  Should  
> [try]
> also do so, or should its complexity be limited?  If limited, what  
> is a
> reasonable limit?  Exact match?  Range match?  Glob match?
>
> It strikes me that there are two possible solutions:
>
> (1) Make [try] an unholy union of [catch] and [switch] such that the
> user can specify the information being switched on (e.g. "%C,%E,%R"
> where %C is the return code, %E is the errorcode and %R is the  
> result).
> You delegate pattern matching to [switch] and allow the user to match
> based on any combination of return code, errorcode and result.
>
>   try {
>     # ...
>   } thenwith -glob -- "%C,%E" {
>     "1,POSIX *" { handle posix errors }
>     "3,*" -
>     "4,*" { handle break/continue }
>   }
I don't think it matters whether [try] delegates to [switch] in the  
implementation. This still results in [try] acquiring the interface  
of [switch].
>
> (2) Make [try] an unholy union of [catch] and [if]/then/else, and
> provide helper functions/operations to match exception/error cases  
> with
> expr.
>
>   try {
>     # ...
>   } handle { [string match "POSIX *" $::errorCode] } {
>     handle posix errors
>   } handle { code() in {3 4} } {
>     handle continue
>   }
>
> Asssuming some minor changes to expr that could look more like:
>
>   try {
>     # ...
>   } handle { errorcode() like "POSIX *" } {
>   } handle { code() in {3 4} } {
>   }
>
> I found the question on Reddit at
> http://www.reddit.com/r/programming/comments/7dgwy/ 
> ask_proggit_where_clauses_in_catch_declarations/?sort=old
> quite interesting in this regard.
I briefly considered something along those lines -- general  
predicates as guards. I think it's getting too complex though. As you  
say, [try] doesn't need to handle all cases -- it just needs to  
handle the most common cases decently. Just ignore me and go with  
glob-matching or prefix matching. So long as non-error exceptions are  
handled well, I'll be happy.
-- Neil
This message has been checked for viruses but the contents of an attachment
may still contain software viruses, which could damage your computer system:
you are advised to perform your own checks. Email communications with the
University of Nottingham may be monitored as permitted by UK legislation.
 | 
| 
     
      
      
      From: Twylite <tw...@cr...> - 2008-11-20 22:14:14
       
   | 
Hi,
> It's not a range - the "-" in switch means "use the same body as the 
> next branch". i.e., the example means codes 0 and 4 (ok and continue), 
> not 0 to 4.
Ah.  My bad.  A violation of rule #2: never engage in a debate when the 
coffee has run out.
I had been thinking about catching ranges of return codes (e.g. all 
user-defined codes as opposed to Tcl-reserved codes) and wasn't paying 
enough attention to the code.
> OK. On reflection I'm willing to concede that adequate error case 
> analysis requires pattern matching of some sort built-in to [try].
Yay :)
>> Of course its entirely possible that some bright spark declares that if
>> the general class of errorCode is "OBJECT" then [lindex $errorCode 1] is
>> an oo::object that is a child of tcl::errorobj, and you want to do
>> class-based matching on said object.  This is probably quite a strong
>> argument against glob matching (as the only option).
> Good point. I hadn't thought of that. That also could handle 
> subtype-based matching.
>> So I have been suggesting "string match" on a structured list as an
>> approximation of the 'isa' / 'typeof' operator.
> Which could be workable: the OO exception just dumps [$self info 
> ancestors] or whatever into the errorCode, so you have something like: 
> [list Error IOError HostUnreachable object12].
That certainly has the potential to work.  Constructing the error object 
(or calling [errorobj throw]) - however you want it to work - would do a 
[return -options {...}] putting the object into the options dict, but 
also [$self info ancestors] into -errorcode.
>> My point here applies most specifically to cases where you don't control
>> the API (which is rather common), but also to cases where you don't want
>> to modify the API - because it will affect other working code that you
>> don't want to refactor, or because you believe that adding a flow
>> control statement like 'break' or 'continue' outside of a flow control
>> construct is an inherently dangerous code practice because developers
>> using APIs don't expect stuff like that.
> Well, all exceptions affect flow control.
I humbly submit that while one can do immensely cool things with Tcl, 
the average developer (indeed, a good number of well-above-average 
developers) will adopt the WTF face when confronted with the idea that a 
procedure can throw a continue.  We live in the Victorianesque era of 
structure code, and one simply does not abide by such vulgarity in 
civilised society.
>>>   proc connect {schemes host user pass} {
>>>      foreach scheme $schemes {
>>>          try {
>>>              return [$scheme connect $host $user $pass]
>>>          } on error BADAUTH {} { continue }
>>>      }
>>>      error "unable to authenticate"
>>>  }
> I agree that errorcode is pretty unlikely to be useful outside of 
> errors, hence making it optional. I still quite like the symmetry of 
> this proposal.
The symmetry is attractive, but I can't help feeling that if we are 
trying to shoehorn exception handling and error handling into the same 
construct then a reasonable amount of asymmetry is expected.  In fact a 
clear distinction between handling errors and handling exceptions may 
make the intent of the code more evident.
>> Matching:
>>   c. Possibly provide for more complex matches involving other fields
>> (result), regular expressions, disjunctive matching, case sensitivity,
>> relational calculus/algebra, whatever.
> Ha! Maybe subsumption based matching using a description logic 
> reasoner? :-)
.. strange ... I though Girl Genius was in the other tab.
> It seems clear that matching on a the return code (or a list of) can 
> be done in O(1). It seems we agree that this can and should be 
> supported. How much to support narrowing down beyond that is the main 
> focus of debate. I'm prepared to agree with you that some sort of 
> errorCode matching would be beneficial. Glob-matching errorCode is 
> reasonably cheap and probably does cover most common cases. Another 
> cheap alternative would be to treat the pattern as simply a list of 
> constants and then match by finding longest common prefix between the 
> matches and the errorcode, which would have worst case O(N) where N is 
> the llength of $errorCode. That also has the advantage that you can 
> compile all catch blocks into a single pattern matching tree 
> (trie-like). While customised pattern matching is appealing, it seems 
> likely to involve extra complexity in both usage and implementation, 
> with probably performance impacts too. User-supplied predicates or 
> customisable pattern matching is likely to be much less efficient, so 
> should probably not be the default.
Mmm ... the performance vs flexibility trade-off.  The compromise is 
usually to allow the user to select which they need for the particular 
case.  This one deserves more thought, possibly at a less reasonable 
hour (like in the morning).
>>   try {
>>     # ...
>>   } thenwith -glob -- "%C,%E" {
>>     "1,POSIX *" { handle posix errors }
>>     "3,*" -
>>     "4,*" { handle break/continue }
>>   }
> I don't think it matters whether [try] delegates to [switch] in the 
> implementation. This still results in [try] acquiring the interface of 
> [switch].
True, but also the full functionality of switch, and any enhancements 
made to switch.  Matching is not limited to a particular approach that 
I/we think is appropriate right now, but has the flexibility to address 
a wide range of needs.
>> (2) Make [try] an unholy union of [catch] and [if]/then/else, and
>> provide helper functions/operations to match exception/error cases with
>> expr.
>>   try {
>>     # ...
>>   } handle { [string match "POSIX *" $::errorCode] } {
>>     handle posix errors
>>   }
> I briefly considered something along those lines -- general predicates 
> as guards. I think it's getting too complex though. As you say, [try] 
> doesn't need to handle all cases -- it just needs to handle the most 
> common cases decently.
Oh, to have a convenient and provably accurate definition of "common 
cases" ;)
> Just ignore me and go with glob-matching or prefix matching. So long 
> as non-error exceptions are handled well, I'll be happy.
I'd prefer not to -- your approach is often different to mine and it 
pays to consider all views and comments when designing a feature like 
this.  I had already started breaking down the separate concerns, look & 
feel and matching options before this particular thread started, and 
noted that expr matching provides the more flexible and potentially most 
consistent [try] command, but is somewhat ugly around the actual match.  
I had been letting this stew for a bit (paid-work time pressures) but it 
looks like decisions need to be taken soon.  Still, I prefer to take a 
good look at all angles of a problem before taking decisions that become 
permanent.
Regards,
Twylite
 |