0
votes

Hi all i need some help on tcl arrays. I have two procedures in tcl something like below :

    proc A {} {
      set lst_A [list];
      if {true} {
        lappend lst_A [::B $file1];
      } else {
        foreach id $allId {
          lappend lst_A [::B $id];
        }
      }
      return $lst_A;
    }

    proc B {fileID} {
      set fileName [::getFileName $fileID];  # getFileName returns filename from fileid
      set tcName "[set ItemName]_[set ItemId]";
      array set tcArrayName {$fileName $tcSpec};
      return $tcArrayName;
    }

Now i want to create an array which will be a key-value pair where key is some kind of file id and value is some name associated with that id. Now the thing is in proc A if condition is true i want to create array with one key-value pair only and then append that array to lst_A which in this case will contain only one item i.e. the array returned. But if the condition is false than i loop through some ids and for each id i call proc B and create array which is then appended to lst_A which in this case will contain multiple key-value paired arrays.

So I wrote the above two procedures and created array after reading about arrays in tcl tutorial. But not sure if this is correct way or the most optimised way.

My ultimate goal is to create a lst_A or i would say should be an array which will either contain just one key-value pair if condition is true or else will be an array with multiple key-value pairs. Since i am creating the array in proc B, i could only think of returning the key-value pair from proc B as an array and then append that array to a list i.e. lst_A in proc A.

Any suggestions???

1

1 Answers

5
votes

You're confusing array's with lists here.

In tcl, you cannot pass arrays into or return them from functions. Also, tcl uses the term "arrays" for what C++ calls "maps" or Perl and Ruby call "hashes" or what Javascript calls "objects". The term comes from the fact that all these things are generically known as "associative arrays" in computer science. So "arrays" in tcl are key-value pairs, not sequences of data.

Arrays don't normally degenerate into values (or what a seasoned tcl programmer would call "strings"). Therefore this:

array set tcArrayName {$fileName $tcSpec};
return $tcArrayName;

generates a syntax error:

can't read "tcArrayName": variable is array

You can actually degenerate the contents of an array into a value that you can then return. But you have to do it manually via the [array get] command:

return [array get tcArrayName]

The above command will return the content of the tcArrayName array in the form of a two element list:

"$fileName $tcSpec"

which also happens to be a string. And that's the actual string: the character '$' followed by the characters 'f', 'i', 'l', 'e', 'N', 'a', 'm', 'e' etc. Not the filename and the concatenated item name and item id. The literal string "$fileName $tcSpec".

That's because you've used the {} grouping in this line of code:

array set tcArrayName {$fileName $tcSpec}

In tcl, {xxx} behaves the same way 'xxx' does in Perl. Basically its a literal string that does not get substituted. If you want tcl to do $ substitution then you need to use "" grouping:

array set tcArrayName "$fileName $tcSpec"

But this is fragile because if fileName contains spaces then it would break the code. A more robust way to do this is:

array set tcArrayName [list $fileName $tcSpec]

But using an array in this case is a bit redundant since all you're doing is initialising it with a key-value list (also known as a dict in more modern versions of tcl. BTW, what version of tcl are you using?) and then immediately discarding it and returning it's content as a key-value list.

Why not simply return that key-value list:

proc B {fileID} {
  set fileName [::getFileName $fileID];  # getFileName returns filename from fileid
  set tcName "[set ItemName]_[set ItemId]"
  return [list $fileName $tcSpec]
}

But we're still not in the clear. If you try to execute B you'll see this error:

can't read "ItemName": no such variable

That's because tcl does not import global variables into functions by default. As such the variables ItemName and ItemId don't exist in B. You either need to import it explicitly via the global command:

proc B {fileID} {
  global ItemName
  global ItemId

  # ...
}

or access it via the global namespace using a fully qualified variable name:

set tcName "[set ::ItemName]_[set ::ItemId]"

So B should look like this:

proc B {fileID} {
  set fileName [::getFileName $fileID];  # getFileName returns filename from fileid
  set tcName "[set ::ItemName]_[set ::ItemId]"
  return [list $fileName $tcSpec]
}

That's B taken care of but A has some problems as well:

can't read "file1": no such variable

We need to modify it a bit to access the variable file1:

proc A {} {
  set lst_A [list];
  if {true} { ;# assuming that there will be a valid condition here
    lappend lst_A [::B $::file1];
  } else {
    foreach id $allId {
      lappend lst_A [::B $id];
    }
  }
  return $lst_A;
}

This should work as you expect.

If you want a little speed up when you use the key-value list then you should really read up on dicts. As is, dicts won't affect the code given above but it does affect the code calling A. If you plan on using dicts then you can replace the lappend call above with dict append. But older versions of tcl don't have dicts.