Version 18 of tailcall

Updated 2010-08-11 16:51:40 by AMG

Like uplevel 1 except that it also causes the current scope to terminate, though the command is looked up in the current context first. Consequence of NRE.

tailcall command ?arg...?
proc fred {} {
 proc fred {} {
     george
 }
proc george {} {
 proc george {} {
      tailcall harry
 }

If I call fred, it's almost as though fred called harry directly, instead of
If I call fred, it's almost as though fred called harry directly, instead of george.  Not so?
[MS]:  yup - all traces of george are gone from the program stack when harry is
[MS] yup - all traces of george are gone from the program stack when harry is called. Now, if harry resolves to a different command in george's current namespace than it would under fred's, the harry that is called is george's and not fred's (no diff if the commands are FQ, of course).
I think this does pretty much what delegation is supposed to do, right?

----

[jima] (2009-10-15) Perhaps this has been asked before or somewhere else...
[jima] 2009-10-15: Perhaps this has been asked before or somewhere else...

I mean, once fred knows that has to call harry directly the bytecodes generated
I mean, once fred knows that has to call harry directly the bytecodes generated would be the ones equivalent to have said:

proc fred {} {

 proc fred {} {
     harry
 }

I reckon I am not familiar with all the internals of Tcl but I find this would I reckon I am not familiar with all the internals of Tcl but I find this would be an interesting thing. Wouldn't this be a new way to have some sort of macros? MS: Currently, tailcall is not bytecompiled. Everything happens at MS As of today tailcall is not byte compiled, everything happens at runtime. That extremely simple example could indeed be bytecoded in a minute, but things get more involved as soon as [fred] has a bit more structure to it: arguments, local variables, namespace issues both for variable and command lookup, multiple exit points with different (or no) tailcall in them, etc. jima: Thanks a lot Miguel for the answer. I see the point. I guess this is jima: Thanks a lot Miguel for the answer. I see the point. I guess this is the same with uplevel 1, isn't it?

proc fred {} {
 proc fred {} {
  uplevel 1 {
   #code here
  }
 }

Would it be interesting to define a case (like a contract) saying if your proc
Would it be interesting to define a case (like a contract) saying if your proc is simple enough then it gets bytecompiled and you get some benefits?
[MS]:  you do not mean "bytecompiled" but rather "inlined into the caller", as
[MS] you do not mean "bytecompiled" but rather "inlined into the caller", as all proc bodies get bytecompiled. There are quite a few other issues with that, especially to accomodate Tcl's dynamic nature. Changing one inlined proc would cause a spoiling of all bytecodes and recompilation of the world, at least with the current approach to bytecode lifetime management. 
----

[AMG]: Sounds a lot like '''exec''' in [Unix shells].  See [execline] for more information on a noninteractive Unix shell where everything is done with exec/tailcall.
[AMG]: Sounds a lot like `exec` in [Unix shells].  See [execline] for more
----
See also [TIP]#[http://tip.tcl.tk/327%|%327]

----
***Interaction with [try]***

===
'''%''' proc foo {} {puts "I'm foo"}
'''%''' proc bar {} {puts "I'm bar"; try { tailcall foo } finally { puts "exitting" }}
''I'm foo''
'''%''' bar
''I'm bar''
''exiting''
''exitting''
===
31-03-2015 [HE] I'm sure ;-) that I don't understood what happend there. Why "exiting" is printed before "I'm foo" when I call bar?
[AMG]: [[foo]] is invoked by replacing [[bar]] which implies the intervening [[[try]]] block must exit before [[foo]] can start.
[wdb]: Apparently, the `tailcall` closes one of the last gaps in
[wdb] Apparently, the command ''tailcall'' closes one of the last gaps in Tcl: Tail recursion as known in [Scheme].

----
**Emulation**

[Lars H], 2010-05-09: As of late, when writing an [uplevel], I've sometimes found myself thinking "That would be slicker with [tailcall], but I can't rely on 8.6 features in this project". Today it occurred to me that one can however use a [proc] to emulate the properties of [tailcall] that would be needed in these cases, and thus provide a route for forward compatibility.

The main situation I've encountered is that of delegating to another command which may make use of [upvar] or [uplevel]. That's basically taken care of by
[NEM]: As a test/demo of how to use this facility, here is a simple benchmark
proc utailcall args {uplevel 2 $args}
 proc utailcall {args} {uplevel 2 $args}

although it's safer to make it

proc utailcall args {return -code return uplevel 2 $args}

 proc utailcall {args} {return -code return [uplevel 2 $args]}

in case the "terminate proc early" aspect of tailcall is relied upon; this in case the "terminate proc early" aspect of tailcall is relied upon; this is easy to do without thinking much about it. Another aspect of tailcall is the name resolution of the called command. Another aspect of tailcall is the name resolution of the called command. This can be done as follows

proc ntailcall {cmd args} {
 proc ntailcall {cmd args} {
        [uplevel 1 [list ::namespace which $cmd]] {*}$args
       [uplevel 1 [list ::namespace which $cmd]] {*}$args
}
 }

but it's almost as easy to do both at the same time

proc untailcall {cmd args} {

 proc untailcall {cmd args} {
        uplevel 2 [list [uplevel 1 [list ::namespace which $cmd]]] $args
       uplevel 2 [list [uplevel 1 [list ::namespace which $cmd]]] $args

}

 }

A word of warning here is that this will produce a very confusing error message A word of warning here is that this will produce a very confusing error message if the command is undefined, as namespace which returns an empty string in that case. A third aspect is that of preserving return levels. A third aspect is that of preserving return levels.

proc rtailcall args {
 proc rtailcall {args} {
    dict incr options -level 2
    return -options $options $result
}
 }

This leaves some extra material in the [errorInfo], but one can probably live
This leaves some extra material in the [errorInfo], but one can probably live with that. Combining the "r" and "u" aspects is straightforward, but will leave even more:

proc rutailcall args {

 proc rutailcall {args} {
    catch {uplevel 2 $args} result options
    dict incr options -level 2
    return -options $options $result
 }

To complete the set, one might just as well write down the combination of the To complete the set, one might just as well write down the combination of the "r" and "n" aspects

proc rntailcall {cmd args} {
 proc rntailcall {args} {
    catch {
       [uplevel 1 [list ::namespace which $cmd]] {*}$args
    } result options
    dict incr options -level 2
    return -options $options $result
 }

and of all three

proc rnutailcall {cmd args} {

 proc rnutailcall {args} {
    catch {
       uplevel 2 [list [uplevel 1 [list ::namespace which $cmd]]] $args
    } result options
    dict incr options -level 2
    return -options $options $result
 }

But note: all of the above will fail if used for tail recursion, as soon as But note: all of the above will fail if used for tail recursion, as soon as the loops get long enough.