Namespaces help structure large Tcl applications, but they add complexity. In particular, command callbacks may have to be handled specially so they execute in the proper namespace. You choose whether or not you need the extra structure and learning curve of namespaces. If your applications are small, then you can ignore the namespace facility.
Using Namespaces
Namespaces can be nested, so you can create a hierarchy of scopes. Namespaces add new syntax to procedure and variable names. A double-colon, ::, separates the namespace name from the variable or procedure name. You use this syntax to reference procedures and variables in a different namespace. The namespace import command lets you name things in other namespaces without the extra syntax. These concepts are explained in more detail in the rest of this chapter.
namespace eval Random {
# Create a variable inside the namespace
variable seed [clock seconds]
# Make the procedures visible to namespace import
namespace export Init Random Range
# Create procedures inside the namespace
proc Init { value } {
variable seed
set seed $value
}
proc Random {} {
variable seed
set seed [expr ($seed*9301 + 49297) % 233280]
return [expr $seed/double(233280)]
}
proc Range { range } {
expr int([Random]*$range)
}
}Example 14-1 defines three procedures and a variable inside the namespace Random. From inside the namespace you can use these procedures and variables directly. From outside the namespace you use the :: syntax for namespace qualifiers. For example, the state variable is just seed within the namespace, but you use Random::seed to refer to the variable from outside the namespace. Using the procedures looks like this:
Random::Random=> 0.3993355624142661
Random::Range 10=> 4
If you use a package a lot you can import its procedures. A package declares what procedures can be imported with the namespace export command. Importing and exporting are described in more detail later. Once you import a procedure you can use it without a qualified name:
namespace import Random::Random
Random=> 0.54342849794238679
variable name ?value? ?name value? ...If you have an array, do not assign a value in the variable command. Instead, use regular Tcl commands after you declare the variable. You can put any commands inside a namespace block:
namespace eval foo {
variable arr
array set arr {name value name2 value2}
}A namespace variable is similar to a global variable because it is outside the scope of any procedures. Procedures use the variable command or qualified names to reference namespace variables. For example, the Random procedure has a variable command that brings the namespace variable into the current scope:
variable seedYou do not have to have a variable command inside the namespace block. It is only useful there to give the namespace variable an initial value. If a procedure has a variable command that names a new variable, it is created in the namespace when it is first set.
::xThe :: syntax does not affect variable substitutions. You can get the value of the global variable x with $::x. Name the namespace variable x with this:
::foo::xA partially qualified name does not have a leading ::. In this case the name is resolved from the current namespace. For example, the following also names the namespace variable x:
foo::xYou can use qualified names with global. Once you do this, you can access the variable with its short name:
global ::foo::x
set x 5
You can play games by redefining commands within a namespace. For example, a namespace could define a procedure named set. To get the built-in set you could use ::set, while set referred to the set defined inside namespace. Obviously you need to be quite careful when you do this.
You can use qualified names when defining procedures. This eliminates the need to put the proc commands inside a namespace block. Example 14-2 repeats the random number generator using qualified names. Random::Init does not need a variable command because it uses a qualified name for seed:
namespace eval Random {
# Create a variable inside the namespace
variable seed [clock seconds]
}
# Create procedures inside the namespace
proc Random::Init { seed } {
set ::Random::seed $seed
}
proc Random::Random {} {
variable seed
set seed [expr ($seed*9301 + 49297) % 233280]
return [expr $seed/double(233280)]
}
proc Random::Range { range } {
expr int([Random]*$range)
}
namespace eval foo {
variable x 1 ;# ::foo::x
}
namespace eval bar {
variable x 2 ;# ::bar::x
namespace foo {
variable x 3 ;# ::bar::foo::x
}
puts $foo::x ;# prints 3
}
puts $foo::x ;# prints 1Partially qualified names can refer to two different objects.
In Example 14-3 the partially qualified name foo::x can reference one of two variables depending on the current namespace. From the global scope the name foo::x refers to the namespace variable x inside ::foo. From the ::bar namespace, foo::x refers to the variable x inside ::bar::foo.
variable x
set x somethingIf you need to give out the name of the variable, then you can use the namespace current command to create a fully qualified name:
trace variable [namespace current]::x r \
[namespace current]::traceproc
The namespace export command goes inside the namespace block, and it specifies what procedures a namespace exports. The specification is a list of string match patterns that are compared against the set of commands defined in a namespace. The export list can be defined before the procedures being exported. You can do more than one namespace export to add more procedures, or patterns, to the export list for a namespace. Use the -clear flag if you need to reset the export list.
namespace export ?-clear? ?pat? ?pat? ...The namespace import command makes commands in another namespace visible in the current namespace. An import can cause conflicts with commands in the current namespace. The namespace import command raises an error if there is a conflict. You can override this with the -force option. The general form of the command is:
namespace import ?-force? namespace::pat ?namespace::pat?...The pat is a string match type pattern that is matched against exported commands defined in namespace. You cannot use patterns to match namespace. The namespace can be a fully or partially qualified name of a namespace.
If you are lazy, you can import all procedures from a namespace:
namespace import Random::*The drawback of this approach is that Random exports an Init procedure, which might conflict with another module you import in the same way. It is safer to import just the procedures you plan on using:
namespace import Random::Random Random::RangeA namespace import takes a snapshot.
If the set of procedures in a namespace changes, or if its export list changes, then this has no effect on any imports that have already occurred from that namespace.
Callbacks and Namespaces
Commands like after, bind, and button take arguments that are Tcl scripts that are evaluated later. These callback commands execute later in the global scope by default. If you want a callback to be evaluated in a particular namespace, you can construct the callback with namespace code. This command does not execute the callback. Instead, it generates a Tcl command that will execute in the current namespace scope when it is evaluated later. For example, suppose ::current is the current namespace. The namespace code command determines the current scope and adds that to the namespace inscope command it generates:
set callback [namespace code {set x 1}]=> namespace inscope ::current {set x 1}
# sometime later ...
eval $callbackWhen you evaluate $callback later, it executes in the ::current namespace. In particular, if there is a namespace variable ::current::x, then that variable is modified. Compare this without namespaces:
set callback {set x 1}
# sometime later ...
eval $callbackIf you need substitutions to occur on the command when you define it, use list to construct it. Using list is discussed in more detail on pages 114 and 305. Example 14-4 wraps up the list and the namespace inscope into the code procedure, which is handy because you almost always want to use list when constructing callbacks. The uplevel in code ensures that the correct namespace is captured; you can use code anywhere:
proc code {args} {
set namespace [uplevel {namespace current}]
return [list namespace inscope $namespace $args]
}
namespace eval foo {
variable y "y value" x {}
set callback [code set x $y]
=> namespace inscope ::foo {set x {y value}}
}The example defines a callback that will set ::foo::x to y value. If you want to set x to the value that y has at the time of the callback, then you do not want to do any substitutions. In that case, the original namespace code is what you want:
set callback [namespace code {set x $y}]=> namespace inscope ::foo {set x $y}
If the callback has additional arguments added by the caller, namespace inscope correctly adds them. For example, the scrollbar protocol described on page 347 adds parameters to the callback that controls a scrollbar.
Introspection
The info commands operation returns all the commands that are currently visible. It is described in more detail on page 150. You can limit the information returned with a string match pattern. You can also include a namespace specifier in the pattern to see what is visible in a namespace. Remember that global commands and imported commands are visible, so info commands returns more than just what is defined by the namespace. Example 14-5 uses namespace origin, which returns the original name of imported commands, to sort out the commands that are really defined in a namespace:
proc Namespace_List {{namespace {}}} {
if {[string length $namespace] == 0} {
# Determine the namespace of our caller
set namespace [uplevel {namespace current}]
}
set result {}
foreach cmd [info commands ${namespace}::*] {
if {[namespace origin $cmd] == $cmd} {
lappend result $cmd
}
}
return $result
}
Wrapping Existing Packages
Suppose you have an existing set of Tcl procedures that you want to wrap in a namespace. Obviously, you start by surrounding your existing code in a namespace block. However, you need to consider three things: global variables, exported procedures, and callbacks.
upvar #0 [namespace current]::$instance state
button .foo -command [namespace current]::callback \
-textvariable [namespace current]::textvar
[incr Tcl] provides classes, inheritance, and protected variables and commands. If you are familiar with C++, [incr Tcl] should feel similar. A complete treatment of [incr Tcl] is not made in this book. Tcl/Tk Tools ( Mark Harrison, O'Reilly & Associates, Inc., 1997) is an excellent source of information. You can find a version of [incr Tcl] on the CD-ROM. The [incr Tcl] home page is http://www.tcltk.com/itcl/.
Namespaces and uplevel
Namespaces affect the Tcl call frames just like procedures. If you walk the call stack with info level, the namespace frames are visible. This means you can get access to all variables with uplevel and upvar. Level #0 is still the absolute global scope, outside any namespace or procedure. Try out Call_Trace from Example 13-4 on page 151 on your code that uses namespaces to see the effect.
Naming Quirks
When you name a namespace, you are allowed to have extra colons at the end. You can also have two or more colons as the separator between namespace name components. These rules make it easier to assemble names by adding to the value returned from namespace current. These all name the same namespace:
::foo::bar
::foo::bar::
::foo::::barThe name of the global namespace can be either :: or the empty string. This follows from the treatment of :: in namespace names.
When you name a variable or command, a trailing :: is treated differently. In the following command a variable inside the ::foo::bar namespace is modified. The variable has an empty string for its name:
set ::foo::bar:: 3If you want to embed a reference to a variable just before two colons, use a backslash to turn off the variable name parsing before the colons:
set x xval
set y $x\::foo=> xval::foo
namespace forget Random::InitYou can rename imported procedures to modify their names:
rename Range rangeYou can even move a procedure into another namespace with rename:
rename Random::Init myspace::Init