| Ticket UUID: | 550789 | ||
| Title: | [break] should take an 'after'-script | ||
| Type: | RFE | Created on: | 2002-04-30 21:28:32 |
| Submitter: | nobody | Assigned to: | dgp |
| Subsystem: | 16. Commands A-H | Severity: | |
| Priority: | 3 Low | Last modified: | 2003-02-25 16:32:31 |
| Status: | Open | Closed by: | |
| Resolution: | None | Closed on: | |
| Version: | None | ||
| 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: | ||||
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. 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. 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
| ||||
