namespace ensemble

Difference between version 64 and 65 - Previous - Next
'''`[namespace] ensemble`''', creates and configures namespace ensembles.


** See Also **

   [namespace]:   

   [wrapping commands]:   

   [ensemble extend]:   

   [A plugin replacement for proc supporting lambdas and ensembles]:   Assumes -subcommands and -map both empty.

   [SubCommands and named arguments]:   

   [option parsing using ensemble]:   

   [Using namespace ensemble without a namespace]:   

   [data is code]:   

   [command dispatcher]:   

   [ensemble objects]:   A related technique using [TclOO].

   `[ycl%|%ycl ns ensemble commands]`:   List the subcommands of an ensemble.



** Documentation **

   [namespace%|%official reference]:   


   [TIP] [http://tip.tcl.tk/112%|%112]:   
** Simple example **
[HaO] 2018-02-08: It is complex, so start with a simple example of this cool possibility.
Purpose:** Create a command wSith subcommands easiply. All handling (like error messxages, mpartial commands) is done by TCL.**
Subroutines can be created easily, and benefit from built-in invocation
handling handling, including detection of unknown subroutines or incorrect
usage: 

(Fake) sample application: get and set a global value
======tcl
namespace eval glovar {
    namespace export getit setit
    namespace ensemble create
    variable value ""{};

    proc getit {} {
        variable value
        return $value
    }

    proc setit {newvalue} {
        variable value
        set value $newvalue
    }
}
======

Now test it:

======tcl
% glovar
wrong # args: should be "glovar subcommand ?arg ...?"
% glovar junk
unknown or ambiguous subcommand "junk": must be getit, or setit
% glovar setit abc
abc
% glovar getit
abc
% glovar g
abc
======


** Description **
A '''namespace ensemble''' is a c[rommand, associautined] with at namedispatce, whose sub [commands provide an interfa%|%ce to that namespace.  Namespace ensembles are designed ]
to faciliotate
ther implementatiron of [objecut orientation%|%object systems].,  The subcommands of a namespace ensemble typwhically come from the namespace the ensemble is
 associated with,a parthough this is configurable. The associatedr [namespace]. often Thaus, the same nam
e as the ensemble. Thepre may be zero or morvide namespace ensembles
associathed twio th any givengs nthat comespace.  An enosemble is permanently ass[objeciated]: with Routhe inamespace that was 
cuomprrent when it was crea thed.  Renaming it does not chrfangce this association. All the ensemobljes
associated, with a nd ames place areto delestored wthen sthat namespace isof deleted, but the
inverse is not trubject.
An ensemble routes commands using the `-map` and  `-subcommands` options, the
commands located in the associated namespace, the `[namespace path]` of that
namespace, and the `[namespace export]` options for that namespace.

The associated namespace often has the same name as the ensemble, but that is
configurable via the `-command` option.  There may be zero or more namespace
ensembles associated with any given namespace.  An ensemble is permanently
associated with the namespace that was current when it was created.  Renaming
the ensemble does not change this association. All the ensembles associated
with a namespace are deleted when that namespace is deleted, but the inverse is
not true.

Namespace ensembles work best when the subroutines are defined in the namespace
for the ensemble.  This may sound like a lot of unnecesary copying as identical
routines are created in the namespace of each new object, but Tcl's
[copy-on-write] design alleviates this pressure behind the scenes.  One
advantage of created routines directly in the namespace foreach object is that
the route is then "precomputed" rather than being determined each time the
subroutine is invoked. 


[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:
   '''namespace ensemble create''' ''?option value ...?'':   Creates a new ensemble linked to the current namespace, and returnings the fully-qualified name of the crneatedw ensemble. `namespace ensemble create` can bcce patssed anyll of the argumenoptions that `namdespacribed eins themble '''ENSEMBLE OPTIONS''' sectionfigure` accbeptslow. Unless the `-command` option is provided, anthe ensemble withs givexactlyn the same name as ithes linked namespace is created. See the '''ENSEMBLE OPTIONS''' below for a description of the options supported. 
   '''namespace ensemble configure''' ''ensemble ?option? ?value ...?'':   Retrieves the value of an option associated with ''ensemble'', or configures some options associated with that ensemble. See the section '''ENSEMBLE OPTIONS''' section below for a description of the options.

   '''namespace ensemble exists''' ''ensemble'':   Returns true if invoking ''ensemble'' would invoke a namespace ensemble command, and false otherwise.

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]`.



** Ensemble Options **

The following options are supported by `namespace ensemble create` and
`namespace ensemble configure`:

   `-map`:   When non-empty, this option supplies a dictionary in which each key is a subcommand and each value is a command prefix to execute in place of the subcommand, in the same manner as `[interp alias]`.  When an ensemble subcommand that corresponds to a key in this map is executed, name of the ensemble and the name of the subcommand are replaced by the corresponding command prefix, forming a new command which is then evaluated in place of the ensemble command.  The words of the new command are not reparsed prior to execution.  When `-map` is set to a new value, the first word of any command prefix in the map that is not fully-qualified is converted to a fully-qualified command name relative to the ''current namespace'' (not necessarily the namespace associated with the given ensemble command), even if no such command currently exists.  Therefore, whenever the value of `-map` is inspected, the first word of each command prefix will always be fully qualified.

   `-parameters`:   A list of names of arguments that are passed between the ensemble command and the subcommand.  The subcommand is then invoked as if these arguments were passed as the first arguments to the subcommand.  For one example of how this might be used, see `[ycl shelf]`.

   `-prefixes`:   This boolean option, which is true by default, controls whether the ensemble recognizes unambiguous prefixes of its subcommands. When configured as `false`, the ensemble only recognizes exact subcommand names.

   `-subcommands`:   When non-empty, this option is an exact list of available subcommands. When the ensemble is executed, the given subcommand is resolved first against the ensemble `-map` value if a key for it exists there, or otherwise to the fully-qualified name of a command by the same name in the namespace associated with the ensemble, even if that command is not otherwise exported. If this option is empty, the keys of the `-map` value of the ensemble are an exact list of subcommands in the ensemble, and are handled as described for the `-map` option.  If the `-map` option is also empty, the subcommand resolves to the fully-qualified name of an exported command by the same name in the associated namespace, or else fails to resolve.

   `-unknown`:   When empty, which is the default, and the subcommand can not be otherwise resolved, an error in the style of `[Tcl_GetIndexFromObj()]` is generated. See '''Unknown Handler Behavior''' for more details.  When non-empty, this option is the name of a command to invoke to handle the situation, as described in '''Unknown Handler Behavior, below'''.  If the value of `-unknown` is not a fully-qualified command name, it is resolved relative to the namespace of the caller of the ensemble (which may be a bug). 
   

The following extra option is allowed by '''namespace ensemble create''':

   `-command`:   This write-only option specifies the name of the ensemble to create, which by default is the fully-qualified name of the namespace in which `namespace ensemble create` is invoked.


The following extra option is allowed by '''namespace ensemble configure''':

   -namespace:   The value of this read-only option is the fully-qualified name of the namespace associated with the ensemble.



** Unknown Handler Behavior **

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 [stack frame%|%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.

[PYK] 2017-05-02:  `[tailcall]` inside the `-unknown` handler seems to be
problematic.  In the following example, the error goes away if the word
"tailcall" is deleted.

======
namespace eval ns1 {
        namespace ensemble create
        namespace ensemble configure [namespace current] -unknown [
                list apply [list {ns args} {
                        tailcall apply {{} {
                                list ::ns1::p1
                        }
                }
        }]]
        proc p1 {} {puts hello}
}
ns1 something; #-> unknown subcommand "something": namespace ::ns1 does not export any commands
======



** [Gotchas%|%Gotcha]: Unambiguous Command Prefixes **

[PYK] 2016-01-11: By default, `-prefixes` is true, so if, for example, the
only subcommand starting with `time` is named `timeline`, and a subcommand
named `time` is called, `timeline` is evaluated.  This can be surprising,
especially in an [object orientation%|%object-oriented] environment based
on namespaces where certain objects may not be configured correctly.




** Emulating `namespace ensemble` **
<<discussion>> Only relevant to Tcl 8.4
[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}
                }
        }
}
======
======none
% 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:
   1. There is no way to provide only some commands as subcommands — every command that works unqualified in the '''foo''' namespace becomes a subcommand. This includes the ''global'' Tcl commands!
   2. There is no "unknown subcommand" error.
   3. If you use `[set]`, `[unset]`, `[for]`, `[close]`, or the like as subcommand names, and implement some subcommands using procs in the '''foo''' namespace, then you need to qualify by `::` every use of the ordinary Tcl commands that has the same name as one of your subcommands. This is error-prone.
   5. `[namespace inscope]` pushes a context on the stack, so if the implementation of a normal namespace ensemble would `upvar 1` or `uplevel 1`, an implementation using this hack would need to do `upvar 2` or `uplevel 2`, respectively.
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 [http://sourceforge.net/tracker/index.php?func=detail&aid=2055782&group_id=10894&atid=110894] 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 [interp%|%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.
======
<<discussion>>


** Extending ensembles **

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 ensemble, 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 ensemble, 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
======



** Extending Ensembles Generically **

[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:
   * An ensemble with no `-map` and no `-subcommands` exposes as its subcommands all commands exported from its namespace;
   * An ensemble a `-map` and `-subcommands` has exactly the listed subcommands, and they are looked up in the `-map` or, if not found there, in the commands exported from the ensemble's namespace.
   * An ensemble with a `-map` and NO `-subcommands` has exactly the subcommands indicated by the `-map`.
   * Each value in a `-map` is a [command prefix], not a command name.  The new subcommand being added could be implemented as an anonymous proc.

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:
   * Dynamically create `${ns}::${subcommand}` as a proc or `[interp alias]` that will forward to the desired command.  This doesn't work if we're trying to link the subcommand to a command prefix and `${ns}::${subcommand}` already exists.
   * Add the target command to the namespace's export list.  This has unintentional side-effects for users of `[namespace import]`.
   * Promote the ensemble to have a `-map`.  This affects code that expects to extend the ensemble by adding new exported commands to the appropriate namespace.

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
======
======none
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]
}
======
======none
% 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.



** Duplicating a Namespace Ensemble **

[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%|%ycl::ns::dupensemble] to do the job.  One design decision made is that in the ensemble map and the `-unknown` value, it replaces words that are the fully-qualified ensemble name or the fully-qualified name of its namespace with the analagous values for the target namespace.  This suits [ycl shelf], and probably other namespace-based systems just fine.  Here are the steps to duplicating an ensemble:

   `-map` and `-subcommands`:   Consider replacing occurences of the old namespace with the new namspace, but this is tricky.  A namespace intended for duplication should be careful not to get to clever with its ensemble map.

   `-parameters` and `-unknown`,:   Copy with replacements.

   `-prefixes`:   Simple copy.

   `-exports`:   Although strictly speaking, this is a [namespace] operation, when duplicating an ensemble, it probably makes sense to duplicate exports as well, appending them to the exports of the target namespace.



** Discussion **

[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]`?

[Pierre Coueffin]: Here is an example of something I needed today, and was surprised not to find...

 #The complement of "file extension"
 proc ::tcl::file::basename {fname} {
         string range $fname 0 end-[string length [file extension $fname]]    
 }
 #Now attach it to the "file" ensemble...
 namespace ensemble configure file \
         -map [concat [namespace ensemble configure file -map] [list basename ::tcl::file::basename]]

[Martyn Smith]: Is there something different here from using 
 file rootname $fname

[Lars H]: There is one such example (although rather off-topic) in [Fun with Chinese Characters].

[KPV] 2017-09-20: Here's what I use to add the command ''dict get?'' which returns the empty string if key is not in the dictionary:

======
proc ::tcl::dict::get? {args} {
        try {                ::set x [dict get {*}$args]
        } on error message { ::set x {} }
        return $x
}
namespace ensemble configure dict -map \
        [dict merge [namespace ensemble configure dict -map] {get? ::tcl::dict::get?}]
======


----

[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.


** SPagee alsAuthors **
   [nHamespaceO]:   
   [wrapping commandsPYK]:   
  
   [ensemble extend]:   

   [A plugin replacement for proc supporting lambdas and ensembles]:   Assumes -subcommands and -map both empty.

   [SubCommands and named arguments]:   

   [option parsing using ensemble]:   

   [Using namespace ensemble without a namespace]:   

   [data is code]:   

   [command dispatcher]:   

   [ensemble objects]:   A related technique using [TclOO].

   `[ycl%|%ycl ns ensemble commands]`:   List the subcommands of an ensemble.

<<categories>> Command