Version 6 of Tip187 in pure Tcl

Updated 2004-04-23 15:37:03

SS: This is a Pure TCL implementation of TIP187. Note that the semantic is the same, including the fact that lambda will never leak. The only difference with the TIP is the "prcedence" of lambda. In this implementation the order is: command lookup, lambda lookup, unknonw call. This should not make any difference.

 # Pure Tcl implementation of TIP 187 (Procedures as Values)
 # Copyright(C) 2004 Salvatore Sanfilippo <antirez (at) invece (dot) org>
 # Under the Same license as Tcl8.4

 namespace eval tip187 {set counter 0}
 rename unknown tip187_orig_unknown

 proc unknown args {
     set func [lindex $args 0]
     set funcargs [lrange $args 1 end]
     if {[llength $func] == 3 && [lindex $func 0] eq {lambda}} {
         set c [incr ::tip187::counter]
         set t [list proc ::tip187::lambda$c [lindex $func 1] [lindex $func 2]]
         if {![catch {uplevel $t}]} {
             set retval [uplevel ::tip187::lambda$c $funcargs]
             rename ::tip187::lambda$c {}
             return $retval
         }
         catch {rename ::tip187::lambda$c {}}
     } else {
         uplevel tip187_orig_unknown $args
     }
 }

 proc lambda {arglist body} {
     list lambda $arglist $body
 }

 ### Variadic MAP version
 proc map {func args} {
     if {[llength $args] > 1} {
         for {set j 1} {$j < [llength $args]} {incr j} {
             if {[llength [lindex $args $j]] != [llength [lindex $args 0]]} {
                 error "Lists of different length as input for \[map\]"
             }
         }
     }
     set result {}
     for {set i 0} {$i < [llength [lindex $args 0]]} {incr i} {
         set callargs {}
         for {set j 0} {$j < [llength $args]} {incr j} {
             lappend callargs [lindex [lindex $args $j] $i]
         }
         lappend result [eval [list $func] $callargs]
     }
     return $result
 }

 ### Examples ###

 set l1 {1 2 3 4 5}
 set l2 {10 20 30 40 50}
 set l3 [map [lambda {x y} {expr $x+$y}] $l1 $l2]
 puts $l3

EB: These examples fails:

 % map [list string length] {az bn dfg}
 invalid command name "string length"
 % lsort -command [lambda {x y} {expr {$x < $y}}] {2 4 1}
 wrong # args: should be "lambda arglist body"

[map] must be used with with a command or a lambda, not a command prefix, and lambda must be list-quoted. Sounds counter-intuitive to me.

 % lsort -command [list [lambda {x y} {expr {$x < $y}}]] {2 4 1}
 4 2 1

SS: It must be list quoted for a lsort specific issue, actually the option does not do what the name is suggesting (i.e. using the -command option as a command), but is using eval. You can't even use a standard procedure if its name contains spaces in the same condition. This lambas works in any place where a command is expected, If instead a "token" is expected it must be list-quoted.

About your first example, it's not correct. The correct version is:

 map [list lambda x {string length $x}] {az bn dgf} ; # => {2 2 3}

But you can kill "list" if you want and just write

 map [lambda x {strnig length $x}] {az bn dgf}

DKF: Actually lsort's guts are even nastier than that. It does something grotty because like that it can at least be a bit faster, and that makes a huge difference for a sorting engine.

Oh, and if you make the first word of the list returned by [lambda] be itself a command, you can make the lambda engine much more robust against such sloppy use.