Version 3 of List Comprehension

Updated 2002-03-18 15:14:24

List Comprehension is sort-of regex (in reverse) for lists: sort of a short hand way to construct lists from other lists. Playing Haskell has an interesting Tcl take on list comprehensions. Here is another:

 # listc - (List Comprehension) constructs a list from an 'expression'
 # operating on items from supplied lists.
 #
 # Synopsis:
 # listc expression vars1 <- list1 [.. varsN <- listN] [condition]
 #
 # where 
 #  'expression' is any Tcl expression (cmd + args)  returning data to populate
 #    the list. This expression is eval'd.
 #
 # 'vars1' is a list of variable identifier representing items from 'list1'. 
 #   The variables are available for use in 'condition' and 'expression'.
 #
 # '<-' is merely syntatical sugar (Haskell).
 #
 # 'list1' is the source list.
 #
 # 'condition' is any Tcl expression (cmd + args) returning 1/ 0 or true/false.
 #   The condition string is eval'd.
 #
 # You can supply any number of varsN <- listN triplets. They are applied as
 # foreach loops, nested as ordered. 'Condition' is eval'd inside the last
 # triplet's loop context.
 #
 proc listc {expression var1 <- list1 args} {
    set res {}

    # Conditional expression (if not supplied) is always 'true'.
    #
    set condition {expr 1}

    # We should at least have one var/list pair.
    #
    lappend var_list_pairs  $var1 $list1

    # Collect any additional var/list pairs.
    #
    while {[llength $args] >= 3} {
      lappend var_list_pairs [lindex $args 0]
      # skip "<-"
      lappend var_list_pairs [lindex $args 2]
      set args [lrange $args 3 end]
    }

    # If there are no more triplets and at least 1 arg left...
    #
    if {[llength $args] > 0} {
      # Grab condition
      #
      set condition [lindex $args 0]
    }


    # Build the foreach commands (for each var/list pair).
    #
    foreach {var list} $var_list_pairs {
      append foreachs "foreach \{$var\} \{$list\} \{"
    }
    # Insert the conditional expression.
    #
    append foreachs {if {[eval $condition]} {lappend res [eval $expression]}}


    # For each foreach, make sure we terminate it with a closing brace.
    #
    foreach {var list} $var_list_pairs {
      append foreachs \}
    }

    # Evaluate the foreachs...
    #
    eval $foreachs

    return $res
 } 

Here are some examples:

 set i [list 1 2 3 4 5 6]
 puts "From a numeric list: $i"

 set l2 [listc {set i} i <- $i]
 puts "\tA copy of the list: $l2"

 set dbl [listc {expr $n*2} n <- $i]
 puts "\tDouble values from list: $dbl"

 set evn [listc {set i}  i <- $i {expr {$i%2 == 0}}]
 puts "\tOnly even numbers: $evn"

 proc digits {str} {
     set lstr [split $str ""]
     return [listc {set d} d <- $lstr {string is digit $d}]
 }

 puts "Just digits from (703)-999-0012= [digits (703)-999-0012]"

 set names1 [list Todd Coram Bob Jones Tim Druid]
 set lf [listc {list "$l,$f"} {f l} <- $names1]
 puts "From ($names1)\n\tLast,first = $lf"

 set l3 [listc {set f} {f l} <- $names1 {string match "T*" $f}]
 puts "From ($names1)\n\tOnly names starting with 't': $l3"

Now, let's get fancy. Given a proc like lisp's setp and a simple lambda:

 proc lset {vars {values {}}} {
    if {$values == {}} {
 foreach var $vars {
     lappend values [uplevel set $var]
 }
 return $values
    }
    uplevel [list foreach $vars $values break]
    return $values
 }

 proc lambda {arglst body} {
    set name [string map {" " _ $ _} [info level 0]]
    proc $name $arglst $body
    set name
 }

You can get even more functional:

 set names2 [list {Todd Coram} {Bob Jones} {Tim Druid}]
 set lf [listc {[lambda nm {lset {f l} $nm; return "$l,$f"}] $n} n <- $names2]
 puts "From ($names2)\n\tAnother last,first = $lf"

This listc proc also supports multiple (nesting) list variables:

 set l4 [listc {list $n1 $n2} n1 <- [list a b c] n2 <- [list 1 2 3]]
 puts "Create a matrix pairing {a b c} and {1 2 3}: \n\t$l4"

-- Todd Coram