Of course, I could add a [foreach] to the code, but that means I have to
touch the code not only at the top, but near the bottom (for the closing
brace) as well, and indent everything in between another level. And what
if I decide other arguments should accept lists, as well? Being lazy, I
decided there ought to be a way to generalize the concept of changing a
proc which expects a scalar to accept a list also, and wrote the
following new kind of control statement:
===== start of code =====
# Iteration function.
proc iterate {args} {
# Check each named argument for possible iteration.
foreach argName $args {
# Get position in formal argument list.
set call [info level -1] ;# gives {proc ?arg ...?}
set argPos [lsearch [info args [lindex $call 0]] $argName]
if {$argPos < 0} {error "No such argument: $argName"}
incr argPos ;# compensate for proc name
# No need to iterate if argument is a scalar.
upvar $argName arg
if {[llength $arg] == 1} {continue}
# Otherwise iterate over each value in list.
foreach val $arg {
set result [uplevel 2 [lreplace $call $argPos $argPos $val]]
}
# Cause caller to return value of last iteration.
return -code return $result
}
return ;# no iteration needed
}
# Sample scripts using iteration function.
proc forward {a b} {
iterate a b ;# same order as formal args
puts "\tI drive a $a$b"
}
proc reverse {a b} {
iterate b a ;# reverse order
puts "\tI drive a $a$b"
}
# Test harness to exercise sample scripts.
proc test script {
puts "Evaluating \"$script\""
eval $script
}
catch {console show}
test {forward Grand Am}
test {forward {Grand Trans} Am}
test {forward Grand {Am Prix}}
puts "(I know it's really spelled Gran Prix.)"
test {forward {Grand Trans} {Am Prix}}
puts "(There's no such thing as a TransPrix... but there should be!)"
test {reverse {Grand Trans} {Am Prix}}
test {forward {small slow high-mileage fuel-efficient gray} { car}}
===== end of code =====
This limits the code modification to a single line near the top, no
matter how many arguments you want to make iterable.
I've been thinking about how to write a proc that would take the name of
another proc and "iteratize" the specified arguments. It could actually
insert an [iterate] statement into the proc body, as shown above, or it
could redefine the proc in other ways. Of course, it's desirable to
minimize namespace pollution, and work in ways that are compatible with
other unknown modifications that may be in place. Any ideas?
This is not an urgent need or anything, I'm just trying to satisfy my
curiousity and become a better Tcl programmer along the way.
>
> This limits the code modification to a single line near the top, no
> matter how many arguments you want to make iterable.
>
> I've been thinking about how to write a proc that would take the name of
> another proc and "iteratize" the specified arguments. It could actually
> insert an [iterate] statement into the proc body, as shown above, or it
> could redefine the proc in other ways. Of course, it's desirable to
> minimize namespace pollution, and work in ways that are compatible with
> other unknown modifications that may be in place. Any ideas?
>
> This is not an urgent need or anything, I'm just trying to satisfy my
> curiousity and become a better Tcl programmer along the way.
I am not sure if I understand the full implications of this idea.
It seems akin to such "functions" as mapping a list of integers to
another
list or filtering items:
set newlist [filter $oldlist item {$item > 10}]
Which could be naively implemented as:
proc filter {oldlist name expression} {
set newlist {}
foreach name $oldlist {
if $expression {
lappend newlist $name
}
}
}
(The name argument is necessary to avoid special names in the expression
and note that there are no braces at if ...)
Have you put this on the Wiki yet?
Regards,
Arjen
Did you mean to write something like:
proc filter {oldlist name expression} {
set newlist {}
foreach $name $oldlist {
if $expression {
lappend newlist [set $name]
}
}
return $newlist
}
where I added $ in [foreach $name ...], append the value of $name rather
than $name, and return the list of values (otherwise, why gather them)?
That lets you filter a list in ways like
% filter {1 2 3 4 5} x {$x%2 == 1}
1 3 5
but that's not what I'm trying to accomplish. Here's an example that
illustrates my situation. Suppose you have an existing procedure:
proc foo bar {
puts "doing something with scalar value $bar"
scalarProc $bar
}
and you now want it to accept a list instead of a scalar, and return the
value associated with the last scalar in the list. The typical way to do
this would be to modify foo like:
proc foo bar {
foreach val $bar {
puts "doing something with scalar value $val"
set result [scalarProc $val]
}
return $result
}
where you have to keep track of result because [foreach] returns an
empty string. My iteration procedure allows you to modify foo like:
proc foo bar {
iterate bar
puts "doing something with scalar value $bar"
scalarProc $bar
}
which localizes the changes to one line, and doesn't affect indentation.
I think I will put this on the Wiki once I've figured out exactly what I
want to say about it!
Stupid - I was too hasty :( Yes, that is what I meant.
I will be looking at your answer in more detail later.
Regards,
Arjen