Lars Hellström | 29 Sep 15:58
Favicon

TIP#327 (tailcall) and custom control structures

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=/

Gmane