Updated 2012-06-20 09:40:03 by RLE

Arjen Markus (19 april 2006) In languages like Fortran 90/95 and MATLAB, you can use expressions on arrays which are treated element-wise. For instance:
```   real, dimension(10) :: a, b
real                :: c

a = 2.0 * b + c```

is evaluated in a way equivalent to:
```   do i = 1,10
a(i) = 2.0 * b(i) + c
enddo```

(to use a Fortran 90/95 example)

I thought it might be nice to have this ability in Tcl as well ...

For the sake of a simple example, I used the "all" function from Fortran:

• It takes one argument, a logical expression using (one-dimensional) arrays, which can be viewed as an array itself.
• It returns true if all elements of that array are true (or if there are none)

The script below turns the expression given as the argument to [all] into a (hidden) procedure that utilizes [foreach] to evaluate the expression for each list element in turn.

Note:

I used the [catch] command to make memoisation trivial, but [catch] is fairly expensive.

Update: I made a small, subtle and somewhat silly mistake - the original catch statement read:
`   if { [catch \$expr result] } { ...`

`   if { [catch {\$expr} result] } { ...`

The implicit eval done by catch caused the command to fail every single time (it tried to fill in the variables u and v and therefore failed). Now the note about [catch] is no longer valid: it was I who failed, not [catch].

The moral: use [catch] with care - you may easily end up catching too many errors.
``` # listexpr.tcl --
#     Using [expr] on lists:
#     if u and v are lists, then [all {\$u > \$v}]
#     returns 1 if all elements of u are larger than the corresponding
#     elements of v and 0 if there is at least one element for which
#     the condition does not hold.
#

# makeListProc --
#     Create a procedure that handles the list expression
# Arguments:
#     expr      The expression to be examined element by element
#     body      The body of the procedure (minus the preliminaries)
# Result:
#     Name of the generated procedure
# Note:
#     The body argument may contain the substrings EXPR and VARS
#     - these are replaced by the expr argument and by the generated
#     list of variables for use in the foreach construct.
#
# Limitations:
#     The expression should not contain subcommands, that is:
#     {\$u > [splice \$v 1]} would not be parsed properly.
#
proc makeListProc {expr body} {

set vars [lsort -unique [regexp -all -inline {\\$[a-zA-Z0-9_]+} \$expr]]

set eachlist ""
set decls    ""

foreach name \$vars {
set vname [string range \$name 1 end]
if { [uplevel 2 "llength \$name"] > 1 } {
append eachlist "\$vname \\$_\$vname "
append decls "upvar 2 \$vname _\$vname\n"
} else {
append decls "upvar 2 \$vname \$vname\n"
}
}

set body [string map [list VARS \$eachlist EXPR \$expr] \$body]

proc \$expr {} \$decls\$body

return \$expr
}

# all --
#     Check if all elements in the lists referred to in an expression
#     comply to that expression
# Arguments:
#     expr         Expression to be checked
# Result:
#     1 if all elements comply, 0 otherwise. If the lists are
#     empty, return 1 too.
#
proc all {expr} {
if { [catch {\$expr} result] } {
makeListProc \$expr \
{
set result 1
foreach VARS {
if { !(EXPR) } {
set result 0
break
}
}
return \$result }

set result [\$expr]
}
return \$result
}
proc all_simple {expr} {
upvar 1 u u
if { [llength \$u] > 1 } {
return [\$expr]
}
}

# main --
#     Simple test case
#
set u {1 2 3 4}
set v {0 1 2 3}

puts "u > v? [all {\$u>\$v}]"
set u {2 3 4 1}
puts "u > v? [all {\$u>\$v}]"

# Measure the time ...
#
proc check {u v} {
set result 0
foreach u1 \$u v1 \$v {
if { ! (\$u1 > \$v1) } {
set result 0
break
}
}
return \$result
}

puts "Do some timing ..."
set dummy [check \$u \$v]

foreach len   {3     10   30   100 300 1000 3000 10000} \
times {10000 3000 1000 300 100 30   10   3    } {
set u {}
set v {}
for {set i 0} {\$i < \$len} {incr i} {
lappend u [expr {2+rand()}]
lappend v [expr {rand()}]
}
puts "Length = \$len: [time {all {\$u>\$v}} \$times] - [time {check \$u \$v} \$times]"
}

# Note: all_simple breaks if we do this:
#    set u 0
#    all_simple {\$u>\$v}
#

puts "How about a combination of lists and scalar variables?"
set u 0.1
puts "v > 0.1? [all {\$v>\$u}]"
set u -0.1
puts "v > -0.1? [all {\$v>\$u}]"```

The output from the (revised) script:
``` u > v? 1
u > v? 0
Do some timing ...
Length = 3: 5 microseconds per iteration - 3 microseconds per iteration
Length = 10: 8 microseconds per iteration - 5 microseconds per iteration
Length = 30: 14 microseconds per iteration - 12 microseconds per iteration
Length = 100: 39 microseconds per iteration - 36 microseconds per iteration
Length = 300: 104 microseconds per iteration - 105 microseconds per iteration
Length = 1000: 332 microseconds per iteration - 353 microseconds per iteration
Length = 3000: 1037 microseconds per iteration - 1034 microseconds per iteration
Length = 10000: 3418 microseconds per iteration - 3458 microseconds per iteration
How about a combination of lists and scalar variables?
v > 0.1? 0
v > -0.1? 1```

... and also fold, filter, map, and zip which are higher-order functions for invoking operations on every member of a list in this way. For instance, the "all" function above can be rewritten as:
``` proc invoke {cmd args} { uplevel #0 \$cmd \$args }
proc zipWith {op xs ys} {
set ret [list]
foreach x \$xs y \$ys { lappend ret [invoke \$op \$x \$y] }
return \$ret
}
proc foldl {op id xs} {
foreach x \$xs { set id [invoke \$op \$id \$x] }
return \$id
}
proc > {a b} { expr {\$a > \$b} }
proc and {a b} { expr {\$a && \$b} }
proc all {op xs ys} { foldl and 1 [zipWith \$op \$xs \$ys] }
# Then, e.g.:
all > \$u \$v```

AM Hm, the message I failed to convey here is that my [all] procedure works on all kinds of expressions as long as the variables referred to are either one-level lists of two or more elements or scalars. The construction with [catch] makes sure that [makeListProc] is called to (re)construct the underlying procedure, if this is the first time this particular expression is used or if the "type" of the variables has changed (causing [expr] to fail).

So, [all {\$u+\$v>\$w}] will work as well.

But I have noted there are quite a few Wiki pages concerned with the subject. Is it time to consolidate this inside some Tcllib module?

AM Yet another page on lists and operations on their elements: Looking at LISP's SERIES extension

schlenk There are some functions in tcllib for things like this, see the tcllib struct::list module.

'''one''' and '''all''' boolean operators

HaO 2011-05-02 An implementation (using the double-evaluation of expr of its argument) of one (true if one true) and all (true if all true) list operators.

Given a list l with values interpreted as booleans:
`set l {1 8 true no}`

a check for at least one true may be done by:
`expr [join \$l ||]`

If the empty list should not cause an error:
`expr [join [linsert \$l end 0] ||]`

The same for any:
`expr [join \$l &&]`

and
`expr [join [linsert \$l end 1] &&]`