scope

Scope refers to the visibility of various contexts when binding names of variables to values, or when resolving command names.

Description

A name is considered lexically-scoped (statically-scoped) when the object it refers to corresponds to the static layout of the program text, and dynamically-scoped when the object it refers to depends on the program state at the moment the variable is resolved at runtime. The scoping rules of Tcl are primarily dynamic, although name resolution rules involving namespaces have a more lexical flavour. namespaces are reasonably static in the sense that each namespace has an explicit location in the namespace hierarchy, a structure which is used to organize variables and commands, and which has a high correlation to the textual layout of namespaces in the program source code. All Commands and non-local variables are associated with some namespace, and are considered to be mainly lexically-scoped. Local variables are truly lexical since their only context is the immediate text of the command in which they are defined. variable and global can make namespace variables visible in a command. Highly-dynamic scoping is achieved via upvar and uplevel, commands which can be used to do great things — terrible, yes, but great.

The features of Tcl described above allow Tcl programs to be written such that they can be read as if they were for the most part lexically-scoped. Tcl's hybrid approach strikes a great balance in maximizing the advantages of both lexical and dynamic scoping while minimizing the disadvantages of each.

The following namespaces can be in scope when resolving some name:

A namespace
Each namespace is located somewhere in the namespace hierarchy, and actually provides two namespaces, one for variables and one for commands. variable references a namespace variable relative to the namespace in which it is invoked, and proc creates a command in the namespace within which it is invoked. Therefore, both variables and commands have a lexically-scoped flavour.
The global namespace
The namespace at the top of the hierarchy, and the only namespace that has as its name the empty string. It is the namespace associated with the top-level call frame, as accessed via uplevel #0. global references the global namespace.
call frame
The local variable scope of an invoked command is a component of its call frame.
hidden
a flat namespace for commands; used by interp hide and interp invokehidden.

In addition to general the namespaces provided by Tcl, various commands maintain resources that are accessed via a value that serves as a handle for the resource:

channel
the name of a channel as understood by commands such as chan, puts, gets, read, and close.
interp
the name of an interpreter as understood by the interp subcommands.

RS:

dynamic scope
determines variable value at runtime - used in older Lisp versions
lexical scope
conserves the value of variables they had at the time a proc was defined, for instance in closures - considered more advanced in Lisp circles.

CMcC believes this isn't true. Firstly, closures can work in either lexically or dynamically scoped languages.

Secondly, Scoping is about how one resolves (or binds) free (or unbound) names. Dynamic binding can only occur at runtime, Static binding can sometimes occur at definition time (which is its supposed advantage).

In dynamically-scoped languages (in, e.g. real Lisp :) the call frame is searched for name->value mappings matching some unbound name - like upvar. In lexically scoped languages (in, e.g. Scheme) the definition environment for the currently executing code is searched for those mappings - like Tcl's variable

NEM One advantage of static scoping is that the environment a function executes in is related to its position in the source code of the program (hence lexical), whereas in dynamic scope the environment is determined by the dynamic flow of control. The advantage is that it should be easier to track down what variable is being referenced. Tcl uses a limited form of static scope for commands (current and global namespaces), but for mutable variables it is quite strict: proc-local scope only unless you explicitly import vars. Many Functional Programming languages are lexically scoped, as is the lambda calculus. Inheritance in OO languages is a form of dynamic scoping. To illustrate, here are two versions of a lambda construct using dicts as environments -- one is statically scoped, the other is dynamically scoped:

# First, some general utilities:
proc extend {env names values} {
    foreach n $names v $values {dict set env $n $v}
    return $env
}
proc capturelevel {{level 0}} {
    incr level
    set env [dict create]
    # Capture the environment of the caller:
    foreach varname [uplevel $level {info locals}] {
        upvar $level $varname var
        dict set env $varname $var
    }
    return $env
}

proc with {__env__ __body__} { dict with __env__ $__body__ }

proc invoke {cmd args} {uplevel 1 $cmd $args}

# Lexically-scoped lambda expressions:
proc lambda-static {params body} {
    # Capture environment in constructor
    set env [capturelevel 1]
    list lambda-static: $params $body $env
}

proc lambda-static: {params body env args} {
    with [extend $env $params $args] $body
}

# Dynamically-scoped lambda expressions:
proc lambda-dynamic {params body} {
    list lambda-dynamic: $params $body
}

proc lambda-dynamic: {params body args} {
    # Capture environment when invoked
    set env [capturelevel 1]
    with [extend $env $params $args] $body
}

# Invoke a lambda inside a dynamic environment with $a defined
proc test {f a} {invoke $f}

A demo of lexical scoping:

proc make-lex a {lambda-static {} {puts "a = $a"}}
set  lambda [make-lex {Lexical scope}]
test $lambda {Dynamic scope}

That should print Lexical scope because the value of $a in the environment of inner is bound at creation-time. Now, the same thing with dynamic scope:

proc make-dyn a {lambda-dynamic {} {puts "a = $a"}}
set  lambda [make-dyn {Lexical scope}]
test $lambda {Dynamic scope}

Which prints Dynamic scope because the value of $a comes from the environment at the point at which we invoke the function.


escargo 2006-01-06: I have wondered whether it would be possible to add an explicit scope command where code could be executed in a (run-time) specified environment, a generalization of uplevel. There would need to be a way of specifying the environment (uplevel would be one way), but if we had continuations then we could use them as well.

NEM: Well, it depends what you mean by a scope. For instance, dict with in 8.5 provides a limited form of what you want (e.g., see my use of it above in the with procedure used for lambdas). It only captures variables, though, not commands. namespace eval provides another form of explicit scoping. Continuations are closures so they don't need a special scope command; you just apply the continuation. Something like the following would be very useful to me:

set scope [scope capture]
scope with [scope extend $scope $names $values] $script

That could be nicely used to create lambdas etc. The capture, with, and extend sub-commands are given in my example of scoping above, all that would be needed (for a basic implementation) would be to put them in a namespace ensemble. Then, our lexical lambda (closure) is just:

proc lambda {params body} {
    set env [uplevel 1 {scope capture}]
    return [list lambda: $params $body $env]
}
proc lambda: {params body env args} {
    scope with [scope extend $env $params $args] $body
}

Note that scope with has different semantics from dict with in that it creates a new local scope (or rather, restores an old local scope) rather than importing it into the current scope, and it also is side-effect-free. An extension would be to make scope capture capture more than just local scalar variables: arrays, commands, namespace scope etc.

See Also

C-like structs and file scope
Tcl_Obj vs Command