Version 8 of List Comprehension

Updated 2002-03-18 15:25:29

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:

 # Synopsis:
 # listc expression vars1 <- list1 [.. varsN <- listN] [condition].
 #
 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]

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

A copy of the list: 1 2 3 4 5 6

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

Double values from list: 2 4 6 8 10 12

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

Only even numbers: 2 4 6

 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]"

Just digits from (703)-999-0012= 7 0 3 9 9 9 0 0 1 2

 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"

From (Todd Coram Bob Jones Tim Druid) Last,first = Coram,Todd Jones,Bob Druid,Tim

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

From (Todd Coram Bob Jones Tim Druid) Only names starting with 't': Todd Tim

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"

From ({Todd Coram} {Bob Jones} {Tim Druid}) Another last,first = Coram,Todd Jones,Bob Druid,Tim

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"

Create a matrix pairing {a b c} and {1 2 3}: {a 1} {a 2} {a 3} {b 1} {b 2} {b 3} {c 1} {c 2} {c 3}

-- Todd Coram