Tcl Source Code

View Ticket
Login
Ticket UUID: 550789
Title: [break] should take an 'after'-script
Type: RFE Version: None
Submitter: nobody Created on: 2002-04-30 21:28:32
Subsystem: 16. Commands A-H Assigned To: dgp
Priority: 3 Low Severity:
Status: Open Last Modified: 2003-02-25 16:32:31
Resolution: None Closed By:
    Closed on:
Description:
It would be very convenient if one could supply to break 
a script that will be evaluated after the loop command 
that the [break] is breaking has ended, but before the 
first command after it. This could be used to break out 
of nested loops:

foreach i {a b c d} {
   foreach j {a b c d} {
      set foo [bar $i $j]
      if {$foo<3} then {
         break {break}
      }
      # Some more processing
   }
   # break in break-script would be evaluated here
   incr sum $foo ; # Some still more processing
}

or to break out of an inner loop and immediately 
continue to the next iteration of an outer:

for {set i 0} {$i<$i_max} {incr i} {
   for {set j 0} {$j<$j_max} {incr j} {
      if {[try_it $i $j]} then {
         set T($i) $j
         break {continue}
      }
   }
   # continue in break-script would be
   # evaluated here
   puts stdout "No solution found for i=$i."
}

To break out of three nested loops, one would similarly 
give the command

break {break {break}}

In want of this feature, I have lately found myself 
making very frequent use of auxiliary variables, so that 
the first example would instead have been

set breaking 0
foreach i {a b c d} {
   foreach j {a b c d} {
      set foo [bar $i $j]
      if {$foo<3} then {
         set breaking 1
         break
      }
      # Some more processing
   }
   if {$breaking} then {break}
   incr sum $foo ; # Some still more processing
}

which is much less elegant (and no doubt slower). Making 
special procedures for loops that I need to break out of 
(so that I can use [return] instead) helps in some 
cases, but is not always appropriate.

As far as the implementation is concerned, this does not 
seem particularly complicated. Since no more than one 
[break] can be going on at any single time, the 
interpreter never needs to keep track of more than one 
break-script. In principle, all the [break] command 
would have to do would be to set some variable (perhaps 
a global variable "breakScript", to go with "errorInfo" 
and "errorCode"?) to the break-script. Then the looping 
commands need, when they catch a TCL_BREAK return, only 
look at this variable to see if there is a special 
script to evaluate or not. Even more streamlined would 
be to have a [break] with a script argument return some 
new code, say, TCL_BREAK_SCRIPT instead of TCL_BREAK.

Some notes:

1. Since break-scripts can make TCL_BREAK and 
TCL_CONTINUE returns, they appearently return a result. 
This can be used to have [for], [foreach], and [while] 
return a non-empty result:

set i_pos\
  [foreach i $some_list {
      if {$A($i)>0} then {break {set i}}
   }]

will set $i_pos to the first $i for which $A($i)>0, or 
to an empty string if there was no such $i. In the case 
of [for], this would not add any extra functionality 
since a separate [set i] commad after the [for] would 
have pretty much the same effect, but in the case of 
[foreach] it allows one to distinguish between not 
finding anything and finding something on the last 
iteration.

2. Procedure bodies and the [catch] command also catch 
TCL_BREAK returns; how should they react to break-
scripts then? In the case of procedure bodies, 
evaluating [break] is normally an error, so there is no 
need to react to the break-script at all. In the case of 
catch there is similarly no need to evaluate the break-
script, as the code [catch]ing might want to inspect it 
(just as with $errorCode); this is why it would make 
sense to put the script in a variable that is accessible 
from Tcl.

3. The mechanism proposed above have many similarities 
to the TCL_EVAL return code suggested in TIP #90. It is 
probably best treated in conjuction with that.

/Lars Hellström
User Comments: nobody added on 2002-05-26 01:28:12:
Logged In: NO 

At 2002-05-10 14:56,  dgp wrote:
> The particular applications outlined in the proposal are
> worthwhile and interesting, but Tcl already provides the
> ability to define new looping commands and new completion
> codes that can implement those ideas.

Whereas this is quite true, it is also the case that the
elegant way of defining these commands is far from obvious,
and the reply would have been much more enlightning if it
had contained an example. As it were, I stumbled upon *the*
(? -- there could be others ...) elegant solution and could
share it with the world on the Wiki (see
http://mini.net/tcl/return), but there was a fair amount of
luck in that.

> Such commands could
> be added to the control package of tcllib, if it is 
> believed necessary to provide such commands broadly.  I
> don't think there's any need to change the core to give
> the proposer the power he seeks.

I hope you will, and I agree the programming convenience I
requested can be found without changing the core. The only
reason I now see for incorporating this into the core is
that there are much more efficient ways of bytecoding
break-with-eval than that which the byte-compiler produces
from the script-level solutions. I suspect incorporation in
the core is the only way to make the compiler produce these
optimal codings (but maybe Tcl9 will have interfaces that
allow extensions to specify optimal bytecode even for
control structures such as break-with-eval?). 

> I do not think this request is a good idea.  It proposes a
> change to [break], but really this is a proposed change to a
> number of commands that respond to the TCL_BREAK 
> completion code.

I hope you are aware that a similar critique could be
directed against TIP#90. There "a number of commands" (more
precisely every command defined by [proc]) are already
exhibiting a special behaviour, but that is not completely
satisfactory and so it needs to be changed. *Note* that I'm
not saying that I want to direct such critique against
TIP#90, I'm merely pointing out that what difference there
is between the proposals is a difference in degree rather
than something specifically distinict.

Indeed, there is a script level workaround to the return
-code versus control::do problem that is quite similar to
the script level implementation of break-with-eval. Consider

proc eproc {name arglist body} {
    interp alias {} $name {} eproc_call "$name "
    proc "$name " $arglist $body
}
proc returneval {script} {return -code -1 $script}
proc eproc_call {args} {
    set code [catch [list uplevel 1 $args] res]
    if {$code == -1} then {
        set code [catch [list uplevel 1 $res] res]
        return -code $code $res
    } elseif {$code == 1} then {
        global errorInfo errorCode
        return -code error -errorinfo $errorInfo -errorcode
$errorCode $res
    } else {
        return -code $code $res
    }
}

With these commands, one can instead of the definition

proc b {} {control::do {return -code error} while 1}

which stumbles on the fact that control::do is a proc, use
the definition

eproc c {} {control::do {returneval {error}} while 1}

which does not. Indeed, even the $errorInfo one gets after
[catch c] is (with the exception of the proc name) identical
to that one gets from [catch a], where [a] is defined by

proc a {} {while 1 {return -code error}}

Is it reasonable to request the use of [eproc] rather than
[proc]? Perhaps not, but it probably isn't substantially
more reasonable to request the use of [breakeval-aware
foreach] and [breakeval {script}] instead of [foreach] and
[break {script}].

/Lars Hellström

dgp added on 2002-05-11 04:56:32:
Logged In: YES 
user_id=80530

For compatibility reasons, I do not think such a
proposal can be considered before Tcl 9.

I do not think this request is a good idea.  It proposes a
change to [break], but really this is a proposed change to a
number of commands that respond to the TCL_BREAK 
completion code.  It's not at all clear how some of
those commands would need to adapt to this change.
([subst] ?  Evaluation of Tk bindings? )

[break] has a very narrow purpose of providing at the
script level the ability to return a TCL_BREAK
completition code.  I do not think it should be
further complexified.

The particular applications outlined in the proposal are
worthwhile and interesting, but Tcl already provides the
ability to define new looping commands and new completion
codes that can implement those ideas.  Such commands could
be added to the control package of tcllib, if it is 
believed necessary to provide such commands broadly.  I
don't think there's any need to change the core to give
the proposer the power he seeks.

I'm leaving the request open for further comment, but
dropping the priority.

dkf added on 2002-05-01 14:57:38:
Logged In: YES 
user_id=79902

TIP #90's Don Porter's baby; this FRQ should probably be
considered by him as well.