Description edit
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.
- 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.
# 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] $scriptThat 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.