Dynamic variable scoping

Richard Suchenwirth 2001-12-17: Another inquiry into programming language concepts that are not (but can easily be) implemented in Tcl.

See Also

Namespace variables
Martin Lemburg: we needed this dynascope-functionality and this functionality but across namespaces instead of scope levels.

Description

Dynamic scoping (indefinite scope and dynamic extent, as used in older LISP versions - see Tcl and LISP) means that not only local variables are visible in a command, but also those in the caller's scope, in caller's caller's scope.. right through to global scope. uplevel gives us the means to search the surrounding scopes, inside out, until we found a match, and then tie a "real" local variable to it via upvar. This nonstandard scoping has to be explicitly demanded by calling the following proc dynascope with the variable names in question:

proc dynascope args {
    set here [info level]
    foreach arg $args {
        for {set level 1} {$level <= $here} {incr level} {
            if [uplevel $level info exists $arg] {
                if {$level > 1} {
                    uplevel 1 upvar [incr level -1] $arg $arg
                }
                break ;# the for loop
            }
        }
    }
}
# Now testing:
set foo 1
set bar {went too far}
proc a {} {set bar 2; set grill Oops..;  b; puts a:bar:$bar}
proc b {} {set grill 3; c; puts b:[info locals]}
proc c {} {
    dynascope foo bar grill self nowhere
    set self here
    puts c:foo:$foo,bar:$bar,grill:$grill,self:$self
    puts c:nowhere:[info exists nowhere]
    set foo 11
    set bar 22
    set nowhere 33
}
a

A cascade of commands is built up: a calls b, b calls c. c uses a set of dynascoped variables that we have placed on the various levels: $foo is global, $bar exists globally and in a (the two are not related), $grill both in a and in b. Dynamic scoping should stop at the nearest match, and hence "shadow" the outer variables of same name. $self is a fake dynascope var which, when not found, is created locally to c, as is $nowhere.

Disclaimer: I can think of no reasonable application for dynascope, but experimenting need not always produce useful results. At least it goes to show again that Tcl's limits are wider than my skull is...


Arjen Markus For several years I had a quotation from Francis Picabia in my room:

Unser Kopf ist rund, damit die Gedanken die Richtung wechseln koennen.

, then English translation being,

Our heads are round, so that our thoughts can change direction.

Experimenting with Tcl in this way is a software version of that statement.


Here is another version of variable scoping posted by kruzalex, dynascope proc leaves called variables local and setting of new values in the c scope does not change the value of the same variable in the caller's body - so it it is the same result as to use upvar 0 for every variable in c scope:

proc dynascope args {
    set here [info level]
    foreach arg $args {
        for {set level 1} {$level <= $here} {incr level} {
            if [uplevel $level info exists $arg] {
                if {$level > 1} {
                    upvar $level $arg var
                    uplevel 1 [list set $arg $var]
                }
                break ;# the for loop
             }
        }
    }
}

Testing:

set foo 1
set bar {went too far}
proc a {} {
    set bar 2
    set grill Oops..
    b
    puts a:bar:$bar
}

proc b {} {
    set grill 3
    c
    puts b:grill:$grill
}

proc c {} {
    dynascope foo bar grill self nowhere
    set self here
    puts c:foo:$foo,bar:$bar,grill:$grill,self:$self
    puts {before changing values}
    puts c:foo:$foo,bar:$bar,grill:$grill
    set grill 4
    set bar 3
    set foo 2
    puts {after the set of new values}
    puts c:foo:$foo,bar:$bar,grill:$grill
}

a
puts $foo