Procs as data structures

Richard Suchenwirth 2002-05-17 - In Streams I wrote: "Procs are data too, in some sense..." To clarify this, procs are data, with the following properties:

  • Procs are global (visible from any call stack depth without need for declaration)
  • Procs are a structure consisting of a list and a string

When a proc's name is evaluated in command position, the list elements are matched with the words following the name, to make a local stack frame, in which the string is then evaluated, and the result of that evaluation is returned to the caller.

But Tcl procs are more than C functions (where the compiler would complain at any breach of C syntax). Tcl procs are compiled (and similar problems may show up at that time) when first called. So what if a proc is never called? Philosophically, it seems to miss its destination, but then again, we can very well define and use a proc without ever calling it.

Imagine we want to build up a database of poems. Many ways are possible for doing this, including - with procs! Consider this code:

 proc SillyPoem {author R.Suchenwirth written 2002-05-17} {
    Hey Doc
    this isn't a real proc
    should you ever call it
    you'll get a shock...
 }

Perfectly legal Tcl, the interpreter doesn't complain. Calling SillyPoem of course leads to a number of error messages, ranging from

 no value given for parameter "author" to "SillyPoem"

to

 invalid command name "Hey"

But this poem wasn't intended to be called anyway. Rather, from any part of a Tcl program, you can access the two structure elements by

 info args SillyPoem ;# returns the list
 info body SillyPoem ;# returns the string

That the args are indeed a list is evident when you put additional spaces between words - info args will always join the args with exactly one space. On the other hand, info body returns the input string exactly, with spaces, newlines and all, so the body is a true string. The list might for instance be used for property-value pairs (like in the example), the string is, well, just a string, and we can store just about everything in it.

Changing a proc's "values" is just slightly more convolved, as all three arguments must be present in a call to proc, but you can get them from where they came:

 proc SillyPoem {author W.Shakespeare written 1567} [info body SillyPoem]
 proc SillyPoem [info args SillyPoem] {yes, just silly}

Again, this is a Tcl trivium for which I can imagine no practical use, but that's how philosophy goes - just thinking of something...


TV Don't forget the defaults for the arguments...

Bwise proc_window does this stuff reasonably well, thought I think it adds one newline too many to end of file. Native version insensitive tcl code, I think.

JMN 2004-05-02 Good Grief TV, Yet another tendrilous interjection that looks suspiciously like an attempt to make your Bwise WalledGarden appear more integrated with the rest of the wiki...

You've been mildly called to account by at least one other wikizen regarding relevance - and it's a testimony to the friendly nature of the Tcl community that relevance and coherence are not 'policed' as such. I much prefer the more or less self-moderated state of affairs we currently have on the wiki, and I don't claim to speak for others in this regard - but please Theo, keep the pet-project evangelism down a bit.

I realize this particular page was last edited a while ago, but it's just one of a few examples of apparent Bwise name-dropping such as button, exec, introspection, idiom, label selection, Good Looking Tk

Don't feel I don't value your contributions to the wiki - I think the electrical-engineering perspective on Tcl is interesting; it's just that I don't feel your Bwise extension has as big a mindshare in Tcl as your extensive wiki-authoring might suggest. (I'd hazard a guess, in part due to your choice of license and meandering style of prose) Feel free to move/remove this commentary at some point TV(and/or wikignomes) - I just wanted to register a friendly protest.


Afterthought: if you let the names of such "data procs" start with a # sign, you can be pretty sure that they won't be called by accident (except if escaped deliberately)..

 % proc # {} {puts hash}
 % # this is treated as a comment
 % \# ;# you'd have to escape the hash sign to call it:
 hash

MS notes that you can even make it a real executable that returns the saved data:

 proc SillyPoem2 {} {
    return [join [lrange [split [info body SillyPoem2] \n] 2 end] \n]
    - Silly Poem Revisited -
           by R.Suchenwirth and M. Sofer written 2004-05-25
    Hey Doc
    this is a real proc
    should you ever call it
    you'll get a shock...
 }

You can also write a builder for such procs

 proc buildDataProc {name str} {
     set basicBody [string map [list %NAME $name] {
         return [join [lrange [split [info body %NAME] \n] 2 end] \n]
     }]
     proc $name {} ${basicBody}$str
 }

Note that the data string str cannot contain unmatched ", {, },[ or ]

... and now I notice I'm being silly ...

 proc buildDataProc {name str} {
     proc $name {} "return \"$str\""
 }

I'd like to see procs actually become data structures. That is, invoking proc creates something akin to a dictionary with key/value pairs for body, arglist, name (optional if it's a lambda), documenation, user defined properties, etc. Widget properties are just additional properties associated with the command, giving you a basic object system. Add an upvar-like command that links command properties together and you've got megawidgets, inheritance and delagation. Rename would just modifiy the name property.

And the possibilities for user-defined properties is cool too. Add documentation to a command. A command could carry around it's own unit tests. You could capture profiling or logging information as new properties. Every command would inherently be its own namespace.

Plus then commands would have value semantics, and the command/value dichotomy would go away.


gold Here are procs from RS and MS loaded in a small program for the etcl console. Note that proc SillyPoem is 1) created, then 2) changed or overwritten in the program, as suggested above. We'll set a limit of 5 in a translator proc and return ? as an error warning. The phrase "trans SillyPoem {1}" resembles the chimera "array syntax on list".

        # autoindent syntax and pretty print from ased editor.
        # written on Windows XP on eTCL
        # working under TCL version 8.5.6 and eTCL 1.0.1
        # gold on TCL WIKI , 12may2011
     package require Tk
       console show
        proc SillyPoem {author W.Shakespeare written 1567} {info body SillyPoem};#RS
        proc buildDataProc {name str} {
            proc $name {} "return \"$str\""
        };#MS
        proc trans {nameb lister} {
            foreach item $lister {
                if { ( ! [ string is integer -strict $item ] ) 
                       || ( $item < 0 ) || ( $item >= 5 ) } {
                    return ?
                }
                return [ lindex [ $nameb ] $item ]
            }
        buildDataProc book2 {boot fit good}
        buildDataProc SillyPoem {author W.Shakespeare written 1567}
        proc any_part_of_tcl_program {} {
            puts [ trans book2 {0} ]
            puts [ trans SillyPoem {1} ]
            puts [ trans book2 {7}]
            puts "[ trans book2 {0} ] [ trans book2 {1} ] [ trans SillyPoem {1}]
            [ trans book2 {2}] [ trans book2 {7}]"
        }
        any_part_of_tcl_program;#end
   etcl console output
        boot
        W.Shakespeare
        ?
        boot fit W.Shakespeare good ?

Your linear string of if statements above can be replaced with this if you always pass in a list of integers:

 if { $item >= 5 } { return ? }
 return [ lindex [ $nameb ] $item ]

If you wanted to error check $item to verify it was an integer first, and in the range 0 .. 4, you could do it this way:

 if { ( ! [ string is integer -strict $item ] ) 
      || ( $item < 0 ) || ( $item >= 5 ) } {
   return ?
 }
 return [ lindex [ $nameb ] $item ]

gold so changed, thanks.