29 Sep 15:58
TIP#327 (tailcall) and custom control structures
From: Lars Hellström <Lars.Hellstrom@...>
Subject: TIP#327 (tailcall) and custom control structures
Newsgroups: gmane.comp.lang.tcl.core
Date: 2008-09-29 13:59:18 GMT
Subject: TIP#327 (tailcall) and custom control structures
Newsgroups: gmane.comp.lang.tcl.core
Date: 2008-09-29 13:59:18 GMT
I'm worried about the interaction between TIP#327's [tailcall] and
custom control structures, e.g. the likes of TIP#329's [try]. It seems
to me that there is a big bunch of unspecified behaviour in this area,
which could shroud breaking some fundamental principles for Tcl, even
if it can be argued that it doesn't break compatibility since the new
behaviour is only triggered by a new command.
First, what works: If one defines
proc test-foreach {prefix list} {
foreach x $list {
tcl::unsupported::tailcall {*}$prefix $x
}
}
then [test-foreach {format -%s-} {foo bar baz}] returns -foo-, as one
should expect from the [return [uplevel ...]] analogy in the TIP.
Second, what's unclear: Suppose I want a try-finally style command with
a body to be evaluated and which does some cleanup afterwards.
Classically (well, post-TIP#90) that should be something like
proc once {body} {
# Before-code
catch {uplevel 1 $body} res opt
# After-code
return -options $opt $res
}
so for test purposes one might consider
proc once {body} {
puts "Before: $body"
catch {uplevel 1 $body} res opt
puts "After: $body"
return -options $opt $res
}
which however raises an error if used as
once {tcl::unsupported::tailcall puts Foo}
Since the test unsupported-T.11 ("tailcall and uplevel") is marked
knownBug,[1] we may assume this is temporary and seek a simplified
implementation that avoids tripping on this bug. Dropping the
[uplevel], we arrive at
proc once {body} {
puts "Before: $body"
catch $body res opt
puts "After: $body"
return -options $opt $res
}
which works, but the command
once {tcl::unsupported::tailcall puts Foo}
writes the following to stdout:
Before: tcl::unsupported::tailcall puts Foo
Foo
After: tcl::unsupported::tailcall puts Foo
whereas I would have expected
Before: tcl::unsupported::tailcall puts Foo
After: tcl::unsupported::tailcall puts Foo
Foo
since the TIP states "the invocation of cmd happens /after/ the
currently executing body returns". OTOH, I really feared that the
behaviour would be
Before: tcl::unsupported::tailcall puts Foo
Foo
-- that [tailcall] would silently circumvent [catch] and thus throw
classical principles for the construction of custom control structures
out the door -- so I suppose I should at least be glad for this piece
of sanity.
Beyond that, there is the problem that there doesn't seem to be a way
to make a proc behave like [tailcall] -- a situation rather similar to
the inability of a proc to fully emulate [return] in the pre-TIP#90 era.
Both problems would disappear if [tailcall] was formalised as being a
new exception, since the equivalent proc should then be something like
proc tailcall {cmd args} {
set cmd [uplevel 1 [list ::namespace which $cmd]]
return -code tailcall [list $cmd {*}$args]
# Or should that be with -level 2, so that the return-from-proc
# machinery handles it?
}
I suppose this is effectively the [return -code $TCL_EVAL] idea that
was discussed back in the days of TIP#90... The same security
precautions apply as they did back then.
The opinion has been raised that no new exception codes may be seized
before Tcl 9.0, in worry that some existing script somewhere out there
might be using them for something else,[2] but in the long run I doubt
there is any other way to make [tailcall] interact sanely with
script-level-defined control structures. Some options for how to do it
concretely are
* Seize a new code, but make its numeric value unspecified
(at least throughout Tcl 8.x). Document that the numeric value
can obtained from [catch {return -level 0 -code tailcall}] or
the like. This allows for changing the numeric value if it
collides with anything, and choosing a gigantic value to begin
with so that collisions are highly unlikely.
* Don't seize a new code, and leave [tailcall] half-magical in
Tcl 8.x. Simultaneously vote on seizing a new code in Tcl 9.0,
and deprecate other uses for it in Tcl 8.x documentation.
* Make [tailcall] a special case of returning TCL_RETURN, flagging
the special status in something else than the returncode (e.g.
non-numeric value of -code in options dictionary). This would
mean [tailcall] is like a naked
return -code tailcall -level 1 {...}
([catch {tailcall ...}] returns 2) rather than
return -code tailcall -level 0 {...}
as would be the case in the two previous approaches.
Lars Hellström
Footnotes:
[1] Tests unsupported-T.10 and unsupported-T.11 also have the
constraint atProcExit. Am I right in suspecting this should be tailcall?
[2] Strangely, there seems to be no such worry concerning new commands
in the :: namespace.
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
RSS Feed