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.
# 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", 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 ExplainingVariables (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 20041015escargo 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 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 provides a limited form of what you want in 8.5 (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.C-like structs and file scope
