Version 4 of Local procedures

Updated 2005-09-21 16:10:27

Richard Suchenwirth 2001-03-01 - All procedures created in Tcl are in the global namespace or one of its descendants, and remain there until explicitly deleted (renamed to the empty string). If we still want a procedure to exist only in a limited scope (inside another proc), we just delete it at end of scope, as shown in Goto in Tcl with the goto proc, like this:

 proc this {} {
      proc that {} {...}
      # ... code that calls "that" ...
      rename that ""
 }

You just have to make sure the proc definitions and renames are balanced. Or, you can wrap that dependency in some sugar, whereafter you can just state, in pretty plain English, that a certain proc is local:

 proc local {"proc" name args body} {
    regsub -all @name {
        set __@name {}
        trace variable __@name u {rename @name "" ;#}
    } $name xbody
    uplevel 1 $xbody
    proc $name $args $body
 }

The life of the proc so defined is tied to a variable (with a slightly mangled name, e.g. __foo) that is created in the caller's scope (uplevel 1). When the calling proc terminates, that variable is unset, which fires the unset trace, which renames away the proc.

The "proc" parameter is not (yet) used. We could wrap other local activities by distinguishing keywords there, but I just couldn't think of other applications...

Now this is for testing: we define a local procedure foo, call it, and return:

 proc try {what} {
    local proc foo {bar} {
        return grill$bar
    }
    foo $what
 }

And this is the test itself:

 puts 1:[info proc foo*] ;# make sure there is no proc foo
 puts [try meat]         ;# call a proc that defines and calls foo
 puts 2:[info proc foo*] ;# make sure foo is no more visible 

which should show grillmeat but no mentions of foo.

To tell the truth (as I did in the first sentence), such procedures are local in that they are not visible from outside, but they can't sufficiently shadow other procs of the same name. So we'd maybe better call them "temporary global". But in instances where we can exclude proc name conflicts (where we generate them), the unset trace serves its purpose quite well, see Lambda in Tcl. Example:

 proc foo {...} {
    set name [makeUniqueName]
    proc $name {...} {
       ...
    }
    ...
    $name arg1...
    ...
    rename $name ""
 }

This way, nested invocations of foo (be that because of recursion, or intermittent events) will not harm each other's local proc. Another way to keep procedures truly local is not to put them in procs, but arrange for a just-in-time evaluator - see Lightweight lambda.


See also Block-local variables | Arts and crafts of Tcl-Tk programming | Category Concept