'''[ycl] shelf''', by [PYK], is a simple yet effective dynamic [object orientation%|%object system] that has much in common with [Programming with Prototypes%|%prototype]-based object systems. ** News ** [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. ** 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. 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 in tcl%|%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. ** Usage ** ====== `package require ycl shelf shelf` ====== ====== `package require ycl shelf tcloo shelf` ====== ** Interface ** 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 [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 in the namespace for the current shelf, assigning the value of ''`name`'' to that variable if provided. '''`.basis`''': Returns the name of the shelf the current shelf was spawned from. '''`.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 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. '''`.cloned`''': A command that is evaluated when the shelf is cloned, The value of the command is usually the name of the new 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`'': 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. '''`.inner`''': 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. '''`.namespace`''': 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` by the same name, inserting the name of the current 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. 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 tha 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. '''`.routines`''': A list of the routines on the current 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. '''`.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 ...` . '''`.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. ** 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 tcloo 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