Version 14 of List Comprehension

Updated 2002-04-24 14:41:59

Python can now do it, oh why oh why can't tcl?

List Comprehensions is a short hand way to construct lists from other lists.

From Haskell: The Craft of Functional Programming by Simon Thompson:

In a list comprehension we write down a description of a list in terms of the elements of another list. From the first list we generate elements, which we test and transform to form elements of the result.

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