Version 0 of Function mapping

Updated 2002-05-27 07:17:41

Richard Suchenwirth 2002-05-26 - Mapping is understood here as: given a function (which may also be a partial command, as the "string length" test shows) and one or more lists, return a list consisting of applying the function to one element each of the input lists. See Lambda in Tcl for a restricted version that takes only one list.

In writing the generalized version below, a foreach command, where the number of loop variables is only known at runtime, is constructed as a string and finally evaluated. What exactly is going on, may become clearer by putsing the resulting command, which for the first test gives

 map list {1 2} {3 4} {5 6}
 foreach 0 [lindex $args 0] 1 [lindex $args 1] 2 [lindex $args 2] {
        lappend res [list $0 $1 $2]
    }

People familiar with other programming languages may wonder what 0,1,2.. do here - they are just legal variable names in Tcl, since the distinction between a constant and a variable value is unambiguous by the prefixed dollar sign. By the way, what we get from using list as function, is transposing a matrix, provided that is is broken into its rows. If we have a matrix M represented as a list of (row) lists, it is most easily transposed by

 set M' [eval map list $M]

where the eval does the required breaking up into row lists. In Python, they have a special function zip for this transposition (not sure what the name zip is supposed to mean), and in Tcl we can easily have that too by just declaring

 interp alias {} zip {} map list

}

 proc map {fun args} {
    set cmd "foreach "
    set fargs {}
    for {set i 0} {$i<[llength $args]} {incr i} {
        append cmd "$i [string map [list @ $i] {[lindex $args @]}] "
        lappend fargs $$i
    }
    append cmd [string map [list @f $fun @a [join $fargs]] {{
        lappend res [@f @a]
    }}]
    set res {}
    eval $cmd
    set res
 }
 #------------------------------------------- self-test
 if {[file tail [info script]]==[file tail $argv0]} {
  proc + args {expr [join $args +]}
  foreach {test expected} {
     {map list {1 2} {3 4} {5 6}} {{1 3 5} {2 4 6}}
     {map + {1 2} {3 4} {5 6}}    {9 12}
     {map "string length" {foo bar grill}} {3 3 5}
  } {
    puts $test
    catch {eval $test} res
    if {$res != $expected} {
        puts *****$res/$expected
    } else {puts OK:$res}
  }
 }

if 0 {In Lisp, this behaves almost like MAPCAR, except that map above runs until all lists are exhausted, while MAPCAR ends when the shortest list is exhausted.}