Tcl ensemble is a dispatcher command. If you oversimplify, you can think of it as an alias. What a dispatcher command does is that it calls another command along with all arguments. Here is an example:
# everbody knows a `file` command, right
file dirname [pwd]
# how about this
::tcl::file::dirname [pwd]
The file
command is an ensemble. Calling file dirname ...
will eventually yield calling
::tcl::file::dirname ...
. If you fast forward, file <cmd>
will (most likely) dispatch to
::tcl::file::<cmd>
.
Note
The file dirname
can be thought of as an alias to ::tcl::file::dirname
. This
is simplification, though, as the ensemble construct/mechanism is more powerful than just that.
Ensemble calling convention is <ensemble> <subcommand> <arg1> ... <argN>
. Then in file dirname [pwd]
,
the file
is the ensemble, dirname
is the subcommand and [pwd]
becomes an argument passed
by the ensemble to the subcommand.
From the user perspective, Tcl ensemble is a means for grouping related Tcl commands. This,
indeed, is the purpose of namespaces, too; ensembles, however, can avoid lengthy command
qualification and can do without using namespace import
(and its risk of name clashing).
Ensembles are coupled to namespaces as they are created by the namespace ensemble
command (or an esemble,
indeed, as it dispatches to ::tcl::namespace::ensemble
). In the simplest case, namespace ensemble create
creates "aliases" to all commands exported by the current namespace. The name of the ensemble aliases with
the name of the current namespace. For example:
namespace eval foo {
namespace export bar baz;
proc bar {} {
puts "[lindex [info level 0] 0]";
}
proc baz {} {
puts "[namespace current]::baz";
}
proc bax {} {
puts "[namespace current]::bax";
}
namespace ensemble create; # <--- simplest way of creating an ensemble
}
foo::bar
foo::baz
foo::bax
foo bar
foo baz
foo bax; # yields an ERROR as `bax` is not exported
Note that the implicit/default name of the new ensemble is the same as [namespace current]
. Hence:
namespace eval ::foo::bar {
namespace export *;
proc bar {} {
puts "[lindex [info level 0] 0]";
}
namespace ensemble create;
}
foo::bar::bar
foo::bar bar
bar bar; # yields an ERROR as `bar` is not an ensemble (nor a command)
Note
Note that the lately created foo::bar
ensemble overrides the formely
defined bar
proc inside the foo
namespace. Hence any of foo::bar
,
::foo::bar
and foo bar
would yield wrong # args: should be "... subcommand ?arg ...?"
.
You can control the name of the ensemble through the -command
option to namespace ensemble create
.
The following example also shows, that you can create as many ensembles for a namespace as you will.
namespace eval ::foo::bar {
namespace export *;
proc bar {} {
puts "[lindex [info level 0] 0]";
}
# creating a 1st ensemble (as an ensemble local to the current namespace)
namespace ensemble create -command foobar;
# creating a 2nd ensemble with a fully qualified names
namespace ensemble create -command ::bar;
}
foo::bar::bar
foo::bar::foobar bar
bar bar
Note
Beware, though, that unless you fully qualify the new ensemble, it will create the ensemble
in the current namespace. Hence the following would create the ::foo::bar::bar
ensemble that
makes it impossible to call the same named bar
routine:
namespace eval ::foo::bar {
namespace export *;
proc bar {} { ... }
namespace ensemble create -command bar;
}
::foo::bar::bar; # error
::foo::bar::bar bar; # error
We can also control how ensemble subcommands are named and, indeed, have more fine grained
control what all subcommands are supported by the eventual ensemble. This is through the
-map <dict>
option to namespace ensemble create
. For example:
namespace eval bar {
proc alpha {args} { puts "[lindex [info level 0] 0] ${args}"; }
proc beta {args} { puts "[lindex [info level 0] 0] ${args}"; }
proc gamma {args} { puts "[lindex [info level 0] 0] ${args}"; }
# !!! notice we export no procedure under this namespace but
# still make any a part of the ensemble
namespace ensemble create -map {
alpha alpha
beta beta
gamma beta
delta ::foo::bar
};
}
bar alpha a b c
bar beta a b c
bar gamma a b c
bar delta; # notice this subcommand dispatches outside of `::bar` namespace
bar omega a b c; # ERROR as `omega` has not been mapped
The example shows quite a few things:
- A subcommand name has nothing to do with the actual command being mapped, as in
bar gamma
. - The same command can be mapped several times, such as
::bar::beta
is mapped tobar beta
andbar gamma
. - Mapping can target any command, even in different namespace, if proberly qualified. Such as
in
bar delta
that dispatches to::foo::bar
.
The ensemble explicit mapping through namespace ensemble create -map ...
can be queried
through namespace ensemble configure -map
. Notice that in the actual mapping the subcommands
got fully qualified.
puts [namespace ensemble configure ::bar -map];
> alpha ::bar::alpha beta ::bar::beta gamma ::bar::beta delta ::foo::bar
Besides querying, namespace ensemble configure
can also alter the ensemble. So we can add
a new subcommand, for example:
# notice we have to fully qualify the `gamma` command if changing
# the ensemble outside its namespace context
namespace ensemble configure ::bar -map \
[dict merge [namespace ensemble configure ::bar -map] {omega ::bar::gamma}]
bar omega a b c; # now ok
Note
The above example of adding a new subcommand shows, too, that the ensemble
manipulation (incl. ensemble create
) is not limited to the namespace it relates
too.
There is a bit more we can do with the -map
mapping. It takes the <key>-<value>
pairs in the form a Tcl dictionary, where <key>
is the subcommand name and the
<value>
is the command to which the ensemble dispatches. The <value>
is indeed
a list, such that the first, mandatory item is the command and the other, optional items
are arguments to be passed along to the command when being dispatched by the ensemble.
Here is an example:
namespace eval ::bar {
namespace ensemble create -map {
beta beta
gamma {beta 1 2}
};
}
bar beta a b c;
> ::bar::beta a b c
# notice that for the dispatching, the (fixed) arguments `1` and `2` from
# the map precede the (optional) arguments given to the ensemble call
bar gamma a b c;
> ::bar::beta 1 2 a b c
Note
Using ensemble for grouping related commands has an important but subtle
advantage over a namespace. The ensemble can name subcommand arbitrarily without
worries of name clashing. For example, no namespace can define the set
command
without clashing with the Tcl core set
(and hence having severe limitations).
Ensemble can, though, easily map set
to whatever it likes. So it is not uncommon
to see something like this:
namespace eval whatever {
proc Set {args} { ... }
namespace ensebmle create -map { set Set ... }
}
whatever set ...
Original TIP 112
Ensembles manual (Tcl 8.5)
Examples of extending Tcl core functions (Tcler's wiki)
namespace eval td {
proc _myproc {} {
set n [dict get [info frame [info frame]] proc];
puts "$n: [namespace current] vs. [namespace which $n]";
}
# Notice different visibility of the target procedure names
# within the namespace.
namespace ensemble create -map {
myproc _myproc
extern ::td::_extern
}
# this will export `_myproc`, but you could not use it as a part
# of `td` ensemble (i.e. trying `td _myproc` would err); however,
# creating the `::sdb` ensemble command will provide access to all
# exported procedures
namespace export _myproc;
namespace ensemble create -command ::sdb;
}
namespace eval ydb {
# import all of `td` namespace
namespace import ::td::*;
# re-export everything
namespace export *;
# create ensemble for all exported commands
namespace ensemble create;
}
# A proc defined outside of the namespace
proc ::td::_extern {} {
puts "[namespace current]";
}
td::_myproc
td myproc
#td _myproc; # error
td::_extern
td extern
puts "---"
sdb _myproc
#sdb myproc; # error
#sdb extern; # error
puts "---"
ydb::_myproc
ydb _myproc
#ydb::_extern; # error, `_extern` not part of `::td` and hence not imported