'''[ycl] shelf''', by [PYK], is a simple yet effective dynamic [object orientation%|%object system] similar in some ways to a [Programming with Prototypes%|%prototype]-based object system. ** News ** [PYK] 2017-05-01: Release of version 0.2. Improved [switch] method, 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 ensemble%|%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 in tcl%|%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 [namespace qualifiers%|%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. '''`.inner`''': Returns the inner shelf, i.e. the shelf that provided the currently-active method to the current shelf. This allows injected shelfs to access both the current shelf and their own inner self, including methods and variables not available to the current shelf. '''`.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. '''`.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]. To evaluate a command through switch at the same level as the caller of `switch`: `[uplevel] 1 [[list $_ 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 [upvar%|%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 [http://chiselapp.com/user/pooryorick/repository/ycl/artifact/a7830a4032ea503f%|%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. <> object orientation | ycl