Version 15 of apply

Updated 2007-12-14 14:27:52 by LV

apply will be a command in 8.5 - see TIP 194 [L1 ] which also gives an illustrative implementation in pure-Tcl. http://www.purl.org/tcl/home/man/tcl8.5/TclCmd/apply.htm

 proc apply {fun args} {
     set len [llength $fun]
     if {($len < 2) || ($len > 3)} {
         error "can't interpret \"$fun\" as anonymous function"
     }
     lassign $fun argList body ns
     set name ::$ns::[getGloballyUniqueName]
     set body0 {
         rename [lindex [info level 0] 0] {}
     }
     proc $name $argList ${body0}$body
     set code [catch {uplevel 1 $name $args} res opt]
     return -options $opt $res
 }

RS wonders, however, how the following simpler take is insufficient:

 proc apply {fun args} {
   foreach {argl body ns} $fun break
   namespace eval $ns [list foreach $argl $args break]
   namespace eval $ns $body
 }

Testing:

 % apply {x {expr $x*$x}} 5
 25
 % apply {{x y} {expr hypot($x,$y)} foo} 3 4
 5.0

NEM The difference is that in your version parameters are persistent namespace variables, rather than local to the procedure. Consider:

 % interp alias {} test1 {} apply {x { expr {$x * $x} }}
 test1
 % interp alias {} test2 {} apply {x { set y [test1 12]; return $x }}
 test2
 % test2 "Hello!"
 12

Which isn't what you want, I expect. - RS: Indeed. In fact, I didn't want namespaces at all :) - just because the other version had them... I started with this:

 proc apply {fun args} {
   foreach {argl body} $fun break
   foreach $argl $args break
   eval $body
 }

Bryan Oakley: I don't like the syntax. Why is the function itself a list? What is wrong with a syntax that lets me use it like this:

    button .b -command [apply arglist body arg1 arg2 ...]

... if you need a namespace it could be:

    button .b -command [apply arglist body -namespace foo arg1 arg2]

... and if you want "-namespace" to not be treated as part of the apply command but rather one of the extra arguments you could do:

    button .b -command [apply arglist body -- -namespace foo arg1 arg2]

MS replied to this on c.l.t [L2 ]. The gist of the answer is: that structure is required in order to be able to cache a bytecompiled body and avoid recompilation at each step. The anonymous function carries all the info necessary for compilation {argList body namespace} in a single Tcl_Obj.

Note also that this makes it easier to have

     set anon [list argList body]
     button .b -command  [list apply $anon arg1 arg2]
     button .b1 -command [list apply $anon arg3 arg4]

and have both buttons share the anonymous function - including the bytecompiled structures.

NEM: It's also pretty easy to create a constructor function (which apply is not):

 proc func {params body args} {
     set ns [uplevel 1 { namespace current }]
     linsert $args 0 ::apply [list $params $body $ns]
 }
 button .b -command [func arglist body arg1 arg2 ...]
 # etc...

I personally use a constructor proc that snapshots the lexical closure into a dict:

 proc func args {
     set ns      [uplevel 1 { namespace current }]
     set env     [uplevel 1 { capture }]
     set params  [linsert [lrange $args 0 end-1] 0 __env__]
     set body    [list dict with __env__ [lindex $args end]]
     return [list ::apply [list $params $body $ns] $env]
 }
 proc capture {} {
     set env [dict create]
     foreach name [uplevel 1 { info locals }] {
         upvar 1 $name var
         catch { dict set env $name $var } ;# no arrays
     }
     return $env
 }

Which I can then use like:

 proc person {name age} { func key { set $key } }
 proc ask {object args} { uplevel 1 $object $args } 
 set neil [person "Neil Madden" 25]
 puts "Neil's age is [ask $neil age]"

I've also on occasion used a further variation that wraps the body in a call to expr. I'll leave "-namespace" as an exercise for the reader.


Also, in the days before Tcl 8.5:

What: apply

 Where: http://www.glinx.com/%7Ehclsmith/plugin.html  ???
 Description: Version of the apply procedure as discussed on
        news:comp.lang.tcl during February, 1997.
        Versions of Tcl C and scripting routines as well as a
        lisp-backquote-like proc are available.  Now supports Tcl 8.x.
 Updated: 09/1999
 Contact: mailto:[email protected] (Hume Smith)

NEM wonders if the c.l.t discussion mentioned is this one: [L3 ]. If so, then the apply mentioned there seems more related to {*} than to our current apply command. Or perhaps a version of this:

 proc old-apply {cmd args} { uplevel 1 $cmd $args }

See also If we had no proc