Version 49 of ycl shelf

Updated 2017-08-29 09:18:40 by pooryorick

ycl shelf, by PYK, is a simple yet effective dynamic object system similar in some ways to a prototype-based object system.

News

PYK 2017-05-01: Release of version 0.2. Improved .switch, propagation of destructors to cloned and spawned shelves.

Description

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. commands can be added to the shelf such that the name of the shelf is automatically passed to the command when called through the ensemble. The shelf is not limited to such commands, and indeed an off-the-shelf shelf contains commands that don't take the name of the shelf as an argument. A shelf isn't offended in the slightest if the name of the ensemble is passed in some other position, or not at all.

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 commands 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 commans for the new shelf are derived from those of the current shelf and adjusted so that they reflect the new shelf instead.

when a shelf is spawned, the namespace of the original shelf is added to the namespace path of the new shelf so that the commands of the original shelf remain available, but this is generally only useful for utilties, 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 commands 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 commands 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.

shelf does not currently utilize TclOO, but it might in the future.

Interface

Methods and subcommands 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 subcommands:

$ 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, and if called with one argument, first assigns that argument to the variable.
.basis
Returns the name of the shelf spawned from, or, for a new shelf, the empty string. basis is used to directly access commands and variables in the shelf from which the current shelf was spawned.
.clone
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 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.
.cloned
Invoked when the shelf is cloned. The absolute name of the shelf is returned.
.commands
A list of the commands on the shelf.
.configure
.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.
configure
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
Arranges 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 the hierarchy.
.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 comands 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.
.inner
Returns 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.
.namespace
Returns the name of the namespace associated with the shelf.
.plug cmdname
Register cmdname as a command that should be made available to a shelf that injects this shelf.
.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.
.spawned
Invoked when the shelf is spawned. The absolute name of the shelf is returned.
.subcmd cmdname
Like method, but doesn't arrange for the name of the ensemble to be appended to command prefix.
.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 basis of the current shelf. This is the shelf equivalent of next for TclOO. Typically .switch is called from within another method of the shelf, so it's common to maintain the level of that method via uplevel or tailcall: uplevel 1 [list $_ .switch mycmd ...], or tailcall $_ .switch mycmd ... .

Utilities

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
shelf
Create a new shelf.

Example

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.