ycl shelf

ycl shelf, by PYK, is a simple yet effective dynamic object system that has much in common with prototype-based object systems.


PYK 2018-03-16
{ycl shelf eightfive shelf} is a small but effective object system for the 8.6-challenged.
PYK 2017-12-19
{ycl shelf tcloo shelf} is a new implementation of {ycl shelf} built on TclOO.
PYK 2017-05-01
Release of version 0.2. Improved .switch, propagation of destructors to cloned and spawned shelves.


Despite its small size, ycl shelf is fully featured and ready for use in real projects. It eschews the more widespread class paradigm in favour of the ability to spawn or clone existing objects.

shelf provides the functionality needed to work with namespace ensembles as objects in a dynamic object system. In this documentation, shelf signifies the namespace ensemble that is the handle for the object. A method is a routine in the ensemble that automatically receives the name of the shelf as its first argument.

When a shelf is deleted the associated namespace is also deleted, and vice-versa.

The code footprint of shelf is quite small, and that's partially because components of a shelf have been refactored into other packages in ycl. ycl ensemble duplicate provides the ability to produce the namespaces for a new shelf, and ycl var provides routines such as $, $.exists, and $.locate for reading variables up the hierarchy of shelves that form the basis of the current shelf.

When a new shelf is cloned or spawned, the routines for the new shelf are derived from those of the current shelf and adjusted so that methods receive the name of the new shelf instead.

when a shelf is spawned, the namespace of the shelf it was spawned from is added to its namespace path. This is generally only useful for utilities, i.e., routines that don't need to know the name of the current shelf. The shelf a shelf was spawned or cloned from is known as the basis, and variables and routines in the basis remain available to the spawned shelf. This mechanism can be used to form a hierarchy of shelves, implementing the prototype pattern.

When a shelf is cloned, the routines and variables in the namespace of the original shelf are actually copied into the cloned shelf, and the namespace of the original shelf is not added to the path of the new shelf. A clone is designed to be as indistinguishable as possible from the shelf it was cloned from. Once it is cloned, it can be transformed to taste.

With TclOO implementation of shelf, ::oo::define can also be used to define methods of shelves. The primary difference is that a method defined with .method is implemented by a command, whereas a TclOO method isn't.


package require ycl shelf shelf
package require ycl shelf tcloo shelf


Routines whose name starts with . are reserved for use by the system, as is the namespace named ASCII NUL (\0) in each shelf.

The primordial shelf has the following routines:

$ name ?value?
Returns the value of the first variable named name in the hierarchy of shelves that form the basis of the shelf. If value is provided, it is first assigned to that variable. The assignment always occurs in namespace of the current shelf. To set variables by the same name in shelves that are the basis of the current shelf, use $.locate or basis to work back to the desired shelf and variable.
$.locate name
Returns the fully-qualified name of the first variable named name in the hierarchy of shelves that form the basis of the shelf, or an error if a matching variable is not found.
$.exists name
Like $.locate, but returns True only if a matching variable was found.
Executed just prior to the deletion of the ensemble.
.apply routine args
like apply, but the first argument to the routine is the name of the shelf, and the routine is evaluated in the namespace of the shelf.
.attribute name
Creates a method named name that returns the current value of the corresponding variable in the namespace for the current shelf, assigning the value of name to that variable if provided.
Returns the name of the shelf the current shelf was spawned from.
Clone the current shelf. Commands, variables, and configuration of the original shelf are copied to the clone, and occurrences of the fully-qualified name of the original shelf as words in the ensemble map are replaced with the full name of the clone, as documented for ycl ns dupensemble. The basis of a clone is the same as the basis of the shelf it was cloned from. The result of .cloned is returned.
A command that is evaluated when the shelf is cloned, The value of the command is usually the name of the new shelf.
.configure key
.configure key val...
Get or set configuration values for the current shelf. Possible value are specified in $doc::.configure for the current shelf, per the docummentation for ycl proc checkargs.
Get or set configuration values for the thing modeled by the current shelf. Operation is the same as for .configure, except that by default, each value is stored in the namespace for the current shelf, in the variable named after the key.
.disposal method
Arrange for method to be invoked when the ensemble is deleted. This command is currently additive. with no way to delete any already-registered scripts, except by using trace directly. It isn't anticipated that a more flexible mechanism would be needed, but it could be added at some point.
.eject shelf
Remove a shelf from above the current shelf.
.eval script
Evaluate script in the namespace of the ensemble.
init args
Initialize the shelf.
.inject shelf
Insert shelf into the hierarchy directly above the current shelf. Any commands in the list of pluggable commands for shelf are made available as methods to the current shelf. If shelf has no list of pluggable commands, then it and its ancestors are always searched as needed when a method is required.
Return the inner shelf, i.e. the shelf that provided the currently-active method to the current shelf. This allows injected shelves to access both the current shelf and their own inner self, including methods and variables not available to the current shelf.
.method cmdname ?command prefix? ?args?
Makes cmdname a method of the current shelf. When the method is invoked, command prefix is looked up relative to the current shelf and invoked with the current shelf as its first argument, followed by args, which in turn are followed by any arguments given at invocation time. If a command prefix is not given, the namespace tail of cmdname is used as a single-item command prefix. This command must be invoked for commands that override a command in any basis, even if a command by the same name is already a method of a basis.
Return the name of the namespace associated with the shelf.
.plug shelf ?command name? ...
Create a new shelf, .inject it into the current shelf, and make each command name a method of the new shelf that calls a method of $shelf having the same name, inserting the name of the current shelf and the name of the injected shelf before any arguments provided by the caller. This gives those methods a handle to the current shelf so that they can act as plugins for it, and also a handle to the injected shelf, which holds the state of the plugin. If no command name argument is provided, The list of names produced $shelf .plugin add is used instead.
.plugin add ?command name? ...
Add each command name to the list of names of commands that can be plugged into another shelf, and return that list.
.plugin set ?command name? ...
Clear and then set the list of names of commands that can be plugged into another shelf, and return that list.
.routine name ?command prefix?
Like method, but doesn't arrange for the name of the shelf to be inserted as the first argument.
A list of the routines on the current shelf.
The name of the shelf the currently-active method was defined on. Similar to the first item of self caller for TclOO. Only guaranteed to be accurate the first time it is called in the body of a method, and only if it is the first method called. in the body of a method.
.spawn name
Create a new shelf and copy the configuration of the original shelf to the new shelf. The namespace of the original shelf and items in its namespace path are prepended to the namespace path of the new shelf so that commands in the original shelf are still available. The ensemble map is adjusted in the same way as it is for clone, i.e. as documented for ycl ns dupensemble. If name is the empty string, an unused name is automatically chosen. The result of .spawn is returned.
Invoked when the shelf is spawned. The absolute name of the shelf is returned.
.switch ?shelf shelf? cmdname ?arg? ...
Resolves the command named cmdname in the context of shelf, and evaluates it as a method of the current shelf. By default, shelf is the current value that would be obtained by calling .inner, and this is the shelf equivalent of next for TclOO. Since the value of .inner can change each time the current shelf is called, the default value for shelf may not be what's expected. Therefore, it is safer to obtain the value of .inner at the beginning of the current script, and then pass it explicitly, e.g. $_ .switch shelf [$inner .basis] next method up. Typically .switch is called from within another method of the shelf, so it's common to evaluate .switch using uplevel or tailcall: uplevel 1 [list $_ .switch mycmd ...], or tailcall $_ .switch mycmd ... .
.vars {varname ?localname}? ...
Each argument is a list where the first item is the name of a variable of the object to use locally, and the second item is the name to give the variable locally. If the second item does not exist, the name provided in the first item is used.
.wrap command
Wrap the current shelf around another command. Thereafter, if a routine can't be resolved on the current shelf, it is appended to the wrapped command along with any other supplied arguments, and the resulting command is evaluated, i.e. {*}$command $name {*}$other_arguments.
.wrapped arg? ...
Append any supplied values to wrapped command and evaluate the resulting command.

When a shelf is created, a command named .my is created in the namespace for the shelf. This is not a method of the shelf, but instead a way to call a method of the shelf from a script that is evaluated in its namespace.

.my arg? ...
Evaluate the arguments as a method of the shelf for this namespace.


package require {ycl shelf util}
asmethod spec
spec is a method specification of the form
args objvars nsvars body
Where args and body are the standard arguments to proc. objvars, is a list of names of variables that belong to the shelf, and nsvars is a list of names of variables in the namespace of the command. asmethod transforms body such that variables objvars and nsvars are linked in the local scope of the procedure to the appropriate variables. This allows the procedure body to be written in an a manner that is agnostic to the particular object system in use, since it need only concern itself with local variables. Other object systems may use the same spec, linking the local variables as appropriate for that system.
shelf uses this internally, so an example, although a bit convoluted at the moment (the refactoring is in progress), can be found here
Create a new shelf.


shelf spawn swallow 
swallow $ airspeed 9 
proc fly _ {
    upvar 0 [$_ $.locate airspeed] airspeed
    puts "$_ is flying at a speed of $airspeed!"
swallow method fly 
swallow fly

swallow spawn european
european $ airspeed 11
european $ airspeed ;# -> 11
european fly

A Different Approach to Initialization

A shelf is commonly given a command named init that serves to set the shelf to some initial state. In contrast with many other object systems, this initialization doesn't occur automatically when a shelf is cloned or spawned. This provides the opportunity to tailor a cloned or spawned shelf before initializing it. Like clone and spawn, init commonly returns the name of the shelf, allowing a pattern like this:

[some_shelf spawn newshelf] init key1 val1 key2 val2 ...
newshelf dostuff...

To assign the full name of the shelf to a variable, particularly when arranging for a name to be automatically chosen, this becomes:

set newshelf [some_shelf spawn {}] init key1 val1 key2 val2 ...
$newshelf dostuff...

Ensemble Methods

A namespace ensemble, or nested namespace ensembles can be used as methods of a shelf. The trick is to use the -parameters switch. Here's an example:

package require {ycl shelf shelf}
namespace import [yclprefix]::shelf::shelf

shelf .spawn {heart of gold}

namespace eval generate {
    namespace ensemble create -parameters _
    namespace export *

    namespace eval infinite {
        namespace ensemble create -parameters _
        namespace export *

        proc improbability _ {
            puts [list $_ probability approaching infinity!]
{heart of gold} .method generate

{heart of gold} generate infinite improbability

In the example above, method arranges for the to be passed to generate, and the -parameters feature of namespace ensembles takes care of passing that argument along along until it reaches the target method, improbability. Note also that the generate, is resolved relative to the namespace of the shelf at the time it is invoked.

See the implementation of ycl matrix for similar examples.