Tip187 in pure Tcl

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 "precedence" of lambda. In this implementation the order is: command lookup, lambda lookup, unknown call. This should not make any difference.

escargo 2011-07-08 - According to [L1 ], this TIP was rejected; neither date nor reason for reject is recorded there.

 # 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 fail:

 % 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 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 lambda 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 {string 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.