Version 27 of scope

Updated 2006-01-05 14:22:52 by escargo

Scope is a context within which to resolve names, binding names to values.

Tcl is capable of both lexical and dynamic scoping. Tcl requires lexical scope for command resolution, and prefers (or defaults to) dynamic scope for variable resolution within [proc].

Tcl has partitioned static scopes - a different binding can exist in each static scope for variables and commands with the same name.

Tcl has many scopes:

namespace is an hierarchal scope for binding the names of commands and variables. [variable] references a variable name in some namespace scope relative to the scope in which it is invoked, and [proc] creates a command in the namespace scope within which it is invoked. For this reason, namespace scope can be considered a lexical scope. Additionally, namespace scope is partitioned into distinct bindings for variable and command names.

global scope is a distinguished namespace scope, equivalent to the namespace :: in some interpreter. global scope is also a distinguished call frame scope - equivalent to upvar #0. Global scope can be referenced by the [global] command.

hidden scope is a per-interpreter scope for interp hidden commands.

call frame scope is the context of some call frame in the current evaluation (accessible via uplevel).

proc local scope is the context within which all otherwise unqualified variable names referenced in a proc are defined. Proc local is a special case of a call frame scope, being the nearest enclosing call frame scope.

channel scope is the per-interpreter global scope for resolving channel names e.g. stdin, file5. There is no eponymous command associated with channels (but it'd be easy enough to fudge.)

interp scope is an hierarchal global scope for resolving interpreters, not to be confused with the use of the command namespace for resolving the eponymous command associated with each interpreter.

package scope is a global for naming packages.


In summary, there are two major kinds of scoping in tcl - dynamic and lexical (or static) scope.

dynamic scope is accessed by default inside a proc, or by means of upvar with unqualified names, and is preferred for variable name resolution.

static or lexical scope is accessed explicitly by namespace encoded names, the global operator, and implicitly by command invocation and by the variable operator. Command resolution prefers dynamic scope.

RS has learnt this distinction, however:

  • 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 capture {} {
     set env [dict create]
     # Capture the environment of the caller:
     foreach varname [uplevel 1 { info locals }] {
         upvar 1 $varname var
         dict set env $varname $var
     }
     return $env
 }
 proc with {self body} { dict with self $body }
 proc invoke {cmd args} { uplevel 1 $cmd $args }
 # Lexically-scoped lambda expressions:
 proc lambda-static {params body} {
     # Capture environment in constructor
     upvar 1 self env
     if {![info exists env]} { set env [uplevel 1 { capture }] }
     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
     upvar 1 self env
     if {![info exists env]} { set env [uplevel 1 { capture }] }
     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", as 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", as the value of $a comes from the environment at the point at which we invoke the function.


Some Dichotomies that Need Explaining

Variables (bindings from name to Tcl_Obj value) can be referenced by static and dynamic scopes. Procs and Commands can only be referenced by static scopes. Why is this? Much of the effort in considering and implementing lambda have to do with avoiding the consequences of this dichotomy.

(NEM -- what about uplevel?)

The differences and similarities between Tcl_Objs and Commands is a fundamental and interesting topic. Tcl_Obj vs Command is a page to discuss it. - on reflection that page is misgiven CMcC 20041015

escargo 5 Jan 2006 - 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 generalizeation 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.


Category Concept