Liam P
Liam P

Reputation: 217

Replace several lines of commands with a single variable in tcl

I know I have been asking a lot of questions but I'm still learning tcl and I haven't found anything that similar to this issue anywhere so far. Is it at all possible to replace a set f commands in tcl with one variable function0 for example?

I want to be able to replace the following code;

set f [listFromFile $path1]
set f [lsort -unique $f]
set f [lsearch -all -inline $f "test_*"]
set f [regsub -all {,} $f "" ]
set len [llength $f]
set cnt 0

with a variable function0 because this same code appears numerous times within the script. I should mention it appears both in a proc and not in a proc

The above code relates to similar script as

while {$cnt < $len} {
    puts [lindex $f $cnt]
    incr cnt
    after 25; #not needed, but for viewing purposes
}

Upvotes: 2

Views: 594

Answers (2)

glenn jackman
glenn jackman

Reputation: 247022

(more of a comment than an answer, but I want the formatting)

One thing to be aware of: $f holds a list, then you use the string command regsub on it, then you treat the result of regsub as a list again.

Use list commands with list values. I'd replace the regsub command with

set f [lmap elem $f {string map {"," ""} $elem} ]

for Tcl version 8.5 or earlier, you could do this:

for {set i 0} {$i < [llength $f]} {incr i} {
    lset f $i [string map {, ""} [lindex $f $i]]
}

Upvotes: 1

Peter Lewerin
Peter Lewerin

Reputation: 13272

Variables are for storing values. To hide away (encapsulate) some lines of code you need a command procedure, which you define using the proc command.

You wanted to hide away the following lines

set f [listFromFile $path1]
set f [lsort -unique $f]
set f [lsearch -all -inline $f "test_*"]
set f [regsub -all {,} $f "" ]
set len [llength $f]
set cnt 0

to be able to just invoke for instance function0 $path1 and have all those calculations made in one fell swoop. Further, you wanted to use the result of calling the procedure in code like this:

while {$cnt < $len} {
    puts [lindex $f $cnt]
    # ...

Which means you want function0 to produce three different values, stored in cnt, len, and f. There are several ways to have a command procedure return multiple values, but the cleanest solution here is to make it return a single value; the list that you want to print. The value in len can be calculated from that list with a single command, and the initialization of cnt is better performed outside the command procedure. What you get is this:

proc function0 path {
    set f [listFromFile $path]
    set f [lsort -unique $f]
    set f [lsearch -all -inline $f test_*]
    set f [regsub -all , $f {}]
    return $f
}

which you can use like this:

set f [function0 $path1]
set len [llength $f]
set cnt 0
while {$cnt < $len} {
    puts [lindex $f $cnt]
    incr cnt
    after 25; #not needed, but for viewing purposes
}

or like this:

set f [function0 $path1]
set len [llength $f]
for {set cnt 0} {$cnt < $len} {incr cnt} {
    puts [lindex $f $cnt]
    after 25; #not needed, but for viewing purposes
}

or like this:

set f [function0 $path1]
foreach item $f {
    puts $item
    after 25; #not needed, but for viewing purposes
}

This is why I didn't bother to create a procedure returning three values: you only really needed one.


glenn jackman makes a very good point (or two points, actually) in another answer about the use of regsub. For completeness, I will repeat it here.

Tcl is a bit confusing because it usually allows string operations (like string substitution) on data structures that aren't formally strings. This makes the language very powerful and expressive, but also means that newbies do not always get the kick in the shins that a regular type system would give them.

In this case you created a list structure inside listFromFile by reading a string from a file and then using split on it. From that point on it's a list and you should only perform list operations on it. If you wanted to take out all commas in your data you should either perform that operation on each item in the list, or else perform the operation inside listFromFile, before splitting the text.

String operations on lists will work, but sometimes the result will be garbled, so mixing them should be avoided. The other good point was that in this case string map is preferable to regsub, if nothing else it makes the code a bit clearer.

Documentation: for, foreach, lindex, llength, lsearch, lsort, proc, puts, regsub, set, split, string, while

Upvotes: 3

Related Questions