Version 19 of apply

Updated 2008-09-16 09:51:11 by nem

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 }

WHD: The Tcl 8.5 man page has this example:

 proc map {lambda list} {
    set result {}
    foreach item $list {
       lappend result [apply $lambda $item]
    }
    return $result
 }

 # Returns {1:a 2:bb 3:cc 4:dddd}
 map {x {return [string length $x]:$x}} {a bb ccc dddd}

This strikes me as really bad style: it means that map can only be used with "lambdas"; you can't use normal commands unless you cast them as lambdas. So what would be better?

It is already common style to pass command prefixes as callbacks. "The cmd argument is a command prefix to which two additional arguments will be added..." Suppose we wrote map like this instead:

 proc map {prefix list} {
    set result {}
    foreach item $list {
       lappend result [{*}$prefix $item]
    }
    return $result
 }

Then we can write

 # Returns {1:a 2:bb 3:cc 4:dddd}
 map {apply {x {return [string length $x]:$x}}} {a bb ccc dddd}

But we can also write

 # Returns {1 2 3 4}
 map {string length} {a bb cc dddd}

An apply expression then becomes just another kind of command prefix.

NEM This is indeed good style, as is always using a constructor function rather than a literal:

 proc lambda {params body args} {
     set ns [uplevel 1 { namespace current }]
     list ::apply [list $params $body $ns] {*}$args
 }
 map [lambda x { ... }] $xs

Lars H: The virtue of "constructor function rather than literal" is highly debatable, IMO. Capturing the namespace context is good, but since that constructor function recreates the lambda Tcl_Obj every time it is called, the above has the severe drawback that the same lambda is going to get recompiled over and over again. A literal lambda persists, so it is only compiled once.

NEM: What's that phrase about premature optimisation? Byte-code compilation is fast and if you are constructing a lambda inside a tight loop then simply move it out. Not properly encapsulating code because of some weird obsession with eliminating a few microseconds is just wrong.


See also If we had no proc