Eric Boudaillier 2004/01/23: I want to show here why lambda in tcl can be useful, and how to do it.
Some Tcl and Tk commands take a script as argument, to be eval'ed in certain circumstance. An example that comes to mind here is the Tk bind command:
bind $w <ButtonPress-1> { DoSomething %W [expr {%x+10}] [expr {%y+10}] }
For bind, these scripts can be seen as glue code between the GUI and the application code.
Other commands, like lsort, don't take a script, but a command prefix: a command prefix acts like a script where one or more arguments are append before being eval'ed. For these commands, there is no way other than defining a procedure to pass as a prefix. This is where lambda can be useful.
lsort -command [lambda {x y} {string compare $x $y}] $elems
expr is a good candidate for being in the body of a lambda: sometimes, there is a need for arithmetics commands, like + or *. With lambda; this is simple, without explicitly defining a command:
lmap [lambda {x} {expr {$x*2}}] $elems
How to implement lambda? The following implementation has learned from all the ones listed in lambda in tcl, to be:
namespace eval lambda { namespace export lambda # Array containing procedure names created for lambdas. variable procsTable } # lambda -- # proc lambda::lambda {paramList body} { variable procsTable # If the lambda doesn't exist, create a new # proc associated. set procKey "$paramList\1$body" if {![info exists procsTable($procKey)]} { variable procId if {![info exists procId]} { set procId 0 } set procName ::lambda::lambda[incr procId] proc $procName $paramList $body set procsTable($procKey) $procName } else { set procName $procsTable($procKey) } # Compute required arguments for calling lambda. set reqArgsLength [llength $paramList] if {[lindex $paramList end] eq "args" || [llength [lindex $paramList end]] == 2} { incr reqArgsLength -1 set paramList [lrange $paramList 0 end-1] while {[llength $paramList] && [llength [lindex $paramList end]] == 2} { incr reqArgsLength -1 set paramList [lrange $paramList 0 end-1] } } return [list ::lambda::lambdaeval $reqArgsLength $procName] } # lambdaeval -- # proc lambda::lambdaeval {reqArgsLength args} { if {[llength $args]-1 >= $reqArgsLength} { return [uplevel 1 $args] } else { return [linsert $args 0 ::lambda::lambdaeval $reqArgsLength] } }
The following code also redefines unknown to allow lambda call without the use of eval. Used as command prefix, this is nearly not necessary. RS: The code does not mention eval, but eval is just the special case "uplevel 0", and uplevel you do :)
# rename unknown command to handle lamdbaeval call # without the need of eval if {![llength [info commands __lambda_unknown]]} { rename unknown __lambda_unknown proc unknown {cmd args} { if {[string equal [lindex $cmd 0] "::lambda::lambdaeval"]} { uplevel 1 $cmd $args } else { uplevel 1 [list __lambda_unknown $cmd] $args } } }
Here is a test to show the performance. I have also a C version of the code, which removes the lambdaeval frame and doesn't need uplevel.
proc sum {x y} {expr {$x+$y}} set sumLambda [lambda {x y} {expr {$x+$y}}] set sumCmd [list sum] time {eval $sumLambda [list 4 3]} 1000 time {eval $sumCmd [list 4 3]} 1000 Tcl lambda : 36 microseconds per iteration C lambda : 23 microseconds per iteration Command : 20 microseconds per iteration