namespace ensemble, a subcommand of namespace, creates and configures namespace ensembles.
A namespace ensemble is a command and a set of subcommands that are associated with some namespace. Namespace ensembles are designed to facilitate the implementation of object systems. namespace ensemble is used to create and configure these ensembles. The subcommands of a namespace ensemble typically come from the namespace the ensemble is associated with, though this is configurable. A namespace ensemble is typically associated with the namespace having the same name as the ensemble, although this is also configurable. There may be zero or more namespace ensembles associated with any given namespace. Whan a namespace ensemble is created, it is permanently associated with the current namespace (the namespace namespace ensemble create was invoked from). The link between an ensemble command and its namespace is maintained even if the ensemble is renamed. All the ensembles associated with a namespace are deleted when that namespace is deleted, but the inverse is not true.
DKF: One key feature of 8.5's ensembles is that they can be bytecode compiled in some circumstances.
Three subcommands of namespace ensemble are defined:
When an ensemble is invoked, its first argument is the name of the ensemble subcommand to evaluate, which is used to look up a command or command prefix which is then evaluated in place of the ensemble with the same arguments that were passed to the ensemble, and a result is returned. It is legal to make the subcommand resolve to another (or even the same) ensemble command. Having been replaced by the looked-up command, the original call to the ensemble is not visible through the use of uplevel or info level.
The following options are supported by namespace ensemble create and namespace ensemble configure:
The following extra option is allowed by namespace ensemble create:
The following extra option is allowed by namespace ensemble configure:
If an -unknown handler is specified for an ensemble, that handler is called when the ensemble is otherwise unable to resolve the name of a requested subcommand. The -unknown handler is called from the context that invoked the ensemble, and receives as its arguments all the words that make up the current ensemble invocation, including the fully-qualified name of the ensemble itself. If the -unknown handler returns the empty string, the ensemble attempts one more time to resolve the subcommand, since it may have just been created by the -unknown handler. If the -unknown handler returns anything else, it is interpreted as a command prefix, the arguments from the original call to the ensemble are appended to it, and the new command is resolved relative to the global scope and then executed in place of the ensemble, exactly as any other ensemble subcommand would be. In all cases, the unknown handler is called at most once for a given call to an ensemble.
When ensemble commands are chained (e.g. if you make one of the commands that implement an ensemble subcommand into an ensemble, in a manner similar to the text widget's tag and mark subcommands) each chained ensemble subcommand is resolved in the context of the caller of the outermost ensemble. That is to say that ensembles do not in themselves place any namespace contexts on the Tcl call stack.
When the -unknown handler is empty, which is the default, and the subcommand is not otherwise resolved, the ensemble generates an error message based on the list of commands that the ensemble has defined (formatted similarly to the error message from Tcl_GetIndexFromObj). If the subcommand is ultimately not resolved, this is the error that is propogated.
It is an error for an -unknown handler to delete its namespace.
Lars H: Does anyone feel up to writing a namespace ensemble emulation for Tcl 8.4? The following is a start, but it lacks most of the features.
proc {namespace ensemble} {subcmd args} { switch -- $subcmd configure { error {namespace ensemble configure not supported (sorry!)} } exists { error {namespace ensemble exists not supported (sorry!)} } create { } default { error "unknown subcommand $subcmd, must be create" } array set Opt $args foreach o {-map -prefixes -subcommands -unknown} { if {[info exists Opt($o)]} then { error "$o option not supported (sorry!)" } } set ns [uplevel 1 {::namespace current}] if {![info exists Opt(-command)]} then { set Opt(-command) $ns } elseif {![string match ::* $Opt(-command)]} then { set Opt(-command) ${ns}::$Opt(-command) } interp alias {} $Opt(-command) {} ::ensemble_dispatch_0 $ns } proc ensemble_dispatch_0 {ns subcmd args} { set cmdL [list] foreach pat [namespace eval $ns {::namespace export}] { eval lappend cmdL [ namespace eval $ns [list ::info commands $pat] ] } set matchL [lsearch -all -inline -glob $cmdL [ string map {* \\* [ \\[ ] \\] ? \\?} $subcmd]*] if {[llength $matchL] == 1} then { uplevel 1 [list ${ns}::[lindex $matchL 0]] $args } elseif {[llength $matchL] > 1} then { error "ambiguous subcommand \"$subcmd\": matches\ [join [lsort -dictionary $matchL] {, }]" } else { error "unknown subcommand \"$subcmd\": must be\ [join [lsort -dictionary $cmdL] {, }]" } }
kruzalex: Playing a little bit with emulation I got a following extension with examples:
proc {namespace ensemble} {subcmd args} { switch -- $subcmd exists { error "namespace ensemble exists not supported (sorry!)" } [subst $subcmd] { } create { } default { error "unknown subcommand $subcmd, must be create" } if {[string equal $subcmd configure]} { array set Opt [lrange $args 1 end] } else { array set Opt $args } foreach o {-prefixes -subcommands -unknown} { if {[info exists Opt($o)]} then { error "$o option not supported (sorry!)" } } if {[string equal $subcmd configure]} { set ns [lindex $args 0] } else { set ns [uplevel 1 {::namespace current}] } if {![info exists Opt(-command)]} then { set Opt(-command) $ns } elseif {![string match ::* $Opt(-command)]} then { set Opt(-command) ${ns}::$Opt(-command) } if {[info exists Opt(-map)]} { interp alias {} $ns {} namespace inscope $ns if {[string equal $subcmd configure]} { foreach {func arg} [lindex $args 2] { namespace eval $ns {} $ns proc $func {} $arg } } else { foreach {func arg} [lindex $args 1] { $ns proc $func "{[ info args ${ns}::${func}] [lindex $arg 1]}" [ info body ${ns}::${func}] } } } interp alias {} $Opt(-command) {} ::ensemble_dispatch_0 $ns } proc ensemble_dispatch_0 {ns subcmd args} { set cmdL [list] set nsL [list] foreach pat [namespace eval $ns {::namespace export}] { eval lappend cmdL [ namespace eval $ns [list ::info commands $pat]] } set matchL [lsearch -all -inline -glob $cmdL\ [string map {* \\* [ \\[ ] \\] ? \\?} $subcmd]*] if {[llength $matchL] == 1} then { uplevel 1 [list ${ns}::[lindex $matchL 0]] $args } elseif {[llength $matchL] > 1} then { error "ambiguous subcommand \"$subcmd\": matches\ [join [lsort -dictionary $matchL] {, }]" } else { if [namespace exists ${ns}::${subcmd}] { foreach pat [namespace eval $ns {::namespace export}] { if {[llength [namespace eval $ns [ list ::info commands $pat]]] == 0} { eval lappend nsL $pat } } if {[llength [lsearch -all $nsL $subcmd]] == 0} { error "ERROR:\"$subcmd\" not known" } else { uplevel 1 [list ${ns}::${subcmd}::${args}] } } else { if ![namespace exists $ns] { error "unknown subcommand \"$subcmd\": must be [ join [lsort -dictionary $cmdL] {, }]" } else { if [llength $args] { uplevel 1 [list ${ns}::${subcmd} $args] } else { uplevel 1 [list ${ns}::${subcmd}] } } } } } namespace eval carrot { namespace export foo bar potato {namespace ensemble} create ;# Creates command ::carrot proc foo {} {puts 1} ;# Exported proc bar {} {puts 2} ;# Exported proc boo {} {puts 3} ;# Not exported namespace eval turnip { ;# Not exported namespace export alpha proc alpha {} {puts 4} ;# Exported proc beta {} {puts 5} ;# Not exported {namespace ensemble} create } namespace eval potato { proc north x {puts 6,$x} ;# Not exported proc south x {puts 7,$x} ;# Not exported # Notice we resolve names locally, and Tcl metachars are not special {namespace ensemble} create -map { north {north [$meta[$chars} south {south [$meta[$chars} } } }
% puts [carrot foo] 1 % puts [carrot bar] 2 % puts [carrot b] 2 % puts [carrot turnip alpha] ERROR: "turnip" not known % puts [carrot::turnip alpha] 4,[$meta[$chars % puts [carrot::turnip::alpha] 4,[$meta[$chars % puts [carrot potato north] 6,[$meta[$chars % puts [carrot::potato north] 6,[$meta[$chars % puts [carrot::potato::north] 6,[$meta[$chars % puts [carrot potato north 6] 6, 6 % puts [carrot potato south] 7,[$meta[$chars % rename ::carrot::potato ::spud % spud north 6,[$meta[$chars % spud south 7,[$meta[$chars % {namespace ensemble} configure spud -map { % north {puts NORTH} south {puts SOUTH} % } % spud north NORTH % spud south SOUTH
Lars H 2008-07-25 (improved 08-18): Revisiting this issue, I came up with the following very crude (but quick) way of emulating an ensemble before Tcl 8.5. Instead of
namespace eval ::foo { namespace export * namespace ensemble create }
do
interp alias {} ::foo {} namespace inscope ::foo [lreplace x 0 0]
Creating further aliases in the foo namespace can even be used to emulate the effect of the -map option!
The reason this works is that a call such as
foo bar baz
gets mapped to
namespace inscope ::foo {} bar baz
which in turn behaves as the
::foo::bar baz
call one would expect from the bar subcommand of the foo namespace ensemble, because the namespace inscope call is equivalent to each of
namespace eval ::foo {} [list bar baz] namespace eval ::foo [concat {} [list bar baz]] namespace eval ::foo [concat [list bar baz]] namespace eval ::foo [list bar baz]
The business with lreplace x 0 0 is to generate the empty list as a pure list, since an impure "" here would lead to shimmering.
There are however several respects in which this doesn't behave as a namespace ensemble:
It does however have the advantage over a separate dispatch proc that it does not increase the -level needed for return, which prior to 8.5 could never be higher than 1.
Warning: If you try the above in Tcl 8.5.0–8.5.4, then it crashes due to a bug [L1 ] in Tcl_ConcatObj, but (provided you don't mind the shimmering) you can still experiment with it if you replace the [lreplace x 0 0] by e.g. \n (any other noncanonical representation of the empty list should work too).
The upvar problem can furthermore be worked around by doing the namespace inscope in another interpreter Concretely:
% interp create helper -safe helper % interp alias {} ::foo helper namespace inscope ::foo \n ::foo % interp alias helper ::foo::bar {} puts {I'm foo bar.} ::foo::bar % interp alias helper ::foo::baz {} puts {I'm foo baz.} ::foo::baz % foo bar I'm foo bar. % foo baz I'm foo baz. % proc pickup {varname} {upvar 1 $varname var; puts "The caller's $varname is $var."} % interp alias helper ::foo::pickup {} pickup ::foo::pickup % proc wrap {script} {eval $script} % set a 2; wrap {set a 3; foo pickup a} The caller's a is 3.
See also ensemble extend. This section should probably be moved there?
GJS: Someone on comp.lang.tcl wanted a way to just use "namespace ensemble add" (which doesn't exist) so I created a small package to take care of this. My original goal was to make it work with namespace, so namespace ensemble add could be used, but I had some trouble getting namespace to work when it was renamed and it is not an ensemble; so the commands are nse add map, nse add subcommand, nse remove map, nse remove subcommand, and nse map.
#Namespace Ensemble Package #Gary J S #usage: # nse add subcommand namespace dict # dict is a dictionary value, keys are the command names keyValues are the # procs. This command adds the commands to the -subcommand option of the # ensemble. The ensemble must already exist before adding commands. # nse add map namespace dict # Same as "subcommand", but adds the commands to the -map option. # nse remove subcommand namespace list # list is a list of commands to remove from the -subcommand option of the # ensemble. There is no error if the command does not exist, it is simply not # removed. # nse remove map namespace list # Same as "subcommand" but removes the commands from the -map option. # nse map namespace # Creates an ensemble consisting of all procs and namespaces in namespace. package require Tcl 8.5 namespace eval ::nse { namespace eval add { proc subcommand {namespace dict} { #create a map of all commands if {[string length [ namespace ensemble configure $namespace -subcommand]]} { #create a map of current commands in ensemble set map [dict create {*}[ namespace ensemble configure $namespace -subcommand]] } #add new values to the map dict for {cmd proc} $dict { dict set map $cmd $proc } #remap the ensemble namespace ensemble configure $namespace -subcommand $map } proc map {namespace dict} { #create a map of all commands if {[string length [ namespace ensemble configure $namespace -map]]} { #create a map of current commands in ensemble set map [dict create {*}[ namespace ensemble configure $namespace -map]] } #add new values to the map dict for {cmd proc} $dict { dict set map $cmd $proc } #remap the ensemble namespace ensemble configure $namespace -map $map } } namespace eval remove { proc subcommand {namespace commands} { #create a map of current commands if {[string length [ namespace ensemble configure $namespace -subcommand]]} { #create a map of current commands in ensemble set map [dict create {*}[ namespace ensemble configure $namespace -subcommand]] } else {return} #remove commands from the mapping set map [dict remove $map {*}$commands] #remap the ensemble namespace ensemble configure $namespace -subcommand $map } proc map {namespace commands} { #create a map of current commands if {[string length [namespace ensemble configure $namespace -map]]} { #create a map of current commands in ensemble set map [dict create {*}[ namespace ensemble configure $namespace -map]] } else {return} #remove commands from the mapping set map [dict remove $map {*}$commands] #remap the ensemble namespace ensemble configure $namespace -map $map } } proc map namespace { #create an ensembe, if it does not already exist if {![namespace ensemble exists $namespace]} { namespace eval $namespace {namespace ensemble create} } #turn all child namespaces into ensembles foreach ns [namespace children $namespace] { #create an ensembe, if it does not already exist if {![namespace ensemble exists $ns]} { namespace eval $ns {namespace ensemble create} } map $ns } #add all commands to ensemble, unless they start with a _ foreach cmd [info commands ${namespace}::*] { if {[string index [namespace tail $cmd] 0] ne {_}} { ::nse::add::map $namespace [ dict create [namespace tail $cmd] $cmd] } } } proc _init {args} { #map the namespace map ::nse namespace ensemble configure ::nse } } nse::_init #package info for pkg_mkIndex package provide NSE 0.3
Twylite 2013-03-08: I was looking for a generic way to add a new subcommand to an existing ensemble. This can be tricky because:
Extending an ensemble that has a -map is fairly straightforward: alter the -map, and update the -subcommands list if it is not empty.
If the ensemble does not have a -map, then it can be extended by adding an exported command (with the name of the subcommand) to the ensemble's namespace, and updating the list of -subcommands if not empty.
But there are corner cases: we may want the subcommand to be linked to a differently named command, or to a command in a different namespace, or to a multi-word command prefix; or the target command not be exported from the ensemble's namespace. To resolve these cases we have several options:
I have chosen the latter approach: it handles all corner cases, and breaks in more predictable ways than the other hacks.
# Find all exported commands for the calling namespace proc nsExported {} { set ns [uplevel 1 {::namespace current}] concat {*}[lmap pattern [namespace eval $ns {::namespace export}] { lsearch -all -inline [info commands ${ns}::*] ${ns}::${pattern} }] } proc nsExtendEnsemble {ensembleCmd subcommand cmdPrefix} { set ensemble [uplevel 1 [list ::namespace which $ensembleCmd]] set nsconfig [namespace ensemble configure $ensemble] set map [dict get $nsconfig -map] set subcmds [dict get $nsconfig -subcommands] # Handle addition to command map in three cases: if {[dict size $map] > 0} { # CASE 1: If there's a map, add to it. dict set map $subcommand $cmdPrefix } else { # CASE 2: If there's no map and this is a trivial expansion then we're # done. A trivial expansion is where cmd is a single-word command with # name $subcommand in the ensemble's namespace, and is already exported set ns [dict get $nsconfig -namespace] set needCmd ${ns}::${subcommand} set nsExportedCmds [namespace eval $ns ::nsExported] if {($cmdPrefix ne $needCmd) || ($cmdPrefix ni $nsExportedCmds)} { # CASE 3: If there's no map and this is a non-trivial expansion, then # promote the ensemble to a map, and add the new subcommand. # The map will comprise all subcommands (that's either the subcommands # list if non-empty, or all exported commands), and we can them empty # the subcommand list. foreach c [expr {$subcmds ne {} ? $subcmds : $nsExportedCmds}] { dict set map [namespace tail $c] ${ns}::[namespace tail $c] } dict set map $subcommand $cmdPrefix set subcmds {} } } # Handle addition to the list of subcommands if {($subcmds ne {}) && ($subcommand ni $subcmds)} { lappend subcmds $subcommand } # Update the ensemble, and we're done namespace ensemble configure $ensemble -map $map -subcommands $subcmds return {} }
RS 2013-12-05: Introspection of an ensemble goes a little bit clumsier than we are used to, in Tcl:
namespace ensemble configure dict -map
append ::tcl::dict::append create ::tcl::dict::create exists ::tcl::dict::exists filter ::tcl::dict::filter for ::tcl::dict::for get ::tcl::dict::get incr ::tcl::dict::incr info ::tcl::dict::info keys ::tcl::dict::keys lappend ::tcl::dict::lappend map ::tcl::dict::map merge ::tcl::dict::merge remove ::tcl::dict::remove replace ::tcl::dict::replace set ::tcl::dict::set size ::tcl::dict::size unset ::tcl::dict::unset update ::tcl::dict::update values ::tcl::dict::values with ::tcl::dict::with
The following delivers nothing:
namespace ensemble configure dict -subcommands
So here is a shortcut that does what I expected:
proc ens_subcmds cmd { dict keys [namespace ensemble configure $cmd -map] }
% ens_subcmds dict append create exists filter for get incr info keys lappend map merge remove replace set size unset update values with
PYK 2014-06-07: Except that -subcommands is a different mechanism from map, and not mutually-exclusive with it. Because of the possibility that -unknown could dynamically bring the command into existence, there isn't a good generic way to determine what the commands of a namespace ensemble are, short of actually invoking the subcommand. Commands like ens_subcmds above will only be useful on a case-by-case basis.
PYK 2014-10-13: It's not as cut-and-dried, as perhaps it might be to duplicate an ensemble. For one thing, the process gets mixed up with duplicating a namespace. I wrote ycl::ns::dupensemble to do the job. It's not completely general because, for example, an ensemble map may have the current namespace baked into it in some command prefix, as long as certain constraints are met, though, duplicating an ensemble is a reasonable operation. Here are some of the considerations:
LV: Anyone feel up to writing an example of using this facility to add a new command to one of the Tcl core namespaces, so that the command was available? Perhaps adding a new math function to expr?
Lars H: There is one such example (although rather off-topic) in Fun with Chinese Characters.
escargo 2005-03-28: Reading the description above about the -command option, and seeing it described as a "write-only option" seems to me to be a problem. Does it make sense to specify an option whose value cannot be read? Is there some other way to determine what value was passed via -command?
WHD: In order to retrieve any of the options, you need the name of the ensemble, i.e., the command name. So reading the value of -command would just give you back the command name you already had.
escargo 2005-03-29: OK. So it's not so much a write-only option as an option that would be pointless to read. (Unless there was something that could be renamed, and you wanted to find its original name, which may not be the case here.)
PYK 2014-06-07: -command is only available for namespace ensemble create, and it's write-only because the command it names doesn't exist yet.