Poet2.2 Manual Prototype Object Extension for Tcl


Poet objects may have attributes called slots, accessed primarily via the slot method.  <object> slot <name> <value> sets a slot of the given <name> on the <object> to the <value>.  <object> slot <name> returns the value of the named slot, if it exists.  If it cannot be found, no error is generated, the null string "" is returned.  You can use the methods hasSlot and findSlot to distinguish between a non-existent slot and a slot that has the value "".  All the methods relating to slots are defined on Object.  Note that Object itself has no slots defined on it.

Slots may be public or private, private slots have names beginning with the underline character '_', for example _localslot. Any slot name not beginning with an underline is a public slot. Private slots are defined only on the specific object they're declared on (a better term might have been local slots).  Public slots are subject to inheritance--if the slot is not found on the current object, each of its ancestors is searched for a slot of that name, and that value is returned. 

% Object construct a
% a construct b
% a slot _local 1
% a slot notlocal 2
% b slot _local
% b slot notlocal
% b hasSlot _local
% b hasSlot notlocal
% b findSlot _local
% b findSlot notlocal
% a findSlot _local
% a findSlot notlocal

If an object is persistent (see Thing), then only its public slots are saved and restored.  So private slots are considered to be the internal, volatile state of an object, while public slots are an object's permanent state and public face.  Note that there is no protection against accessing an object's private slots, any slot can be modified or removed from any object anywhere in your program.

Active Slots

Poet slots can be activated so that a method is invoked when it is read or written. Poet uses the symbol '>' for "writing" (imagine the tip of a pencil) and '<' for reading.  When a slot named alpha that is active for writing is written, a method named alpha> will be invoked on $self (the object where the slot is being written) with the new value as its argument. If the slot is active for reading, the method invoked when it is read is alpha<, also with the value as an argument.  A slot can be active for reading, writing, both, or neither, but the most common usage is to make a slot write-active.  The slotname> method is invoked before the slot is actually set, and can refuse to accept the new value by setting the slot to an acceptable value. Slots are made active with the slotOn method.

% Object construct a
% a slot alpha 0
% a method alpha> {value} {puts "set alpha to $value"}
% a slotOn alpha >
set alpha to 0
% a slot alpha 10
set alpha to 10
% a method alpha> {value} {
if {$value > 512} {$self slot alpha 512}
puts "attempt to set alpha to $value"
% a slot alpha 20
attempt to set alpha to 20
% a slot alpha 2000
attempt to set alpha to 2000
% a slot alpha

Note that the slotname> or slotname< method is subject to inheritance, so an object may contain an active slot and inherit the slot's behavior from an ancestor.

Constrained Slots

Poet slots can have their values automatically computed using a formula using the method slotConstrain, which takes the name of the slot as an argument.  When constrained, a formula with the same name as the slot will be used to compute its value.  A formula is like a method, its return value becomes the value of the slot, except that formulas do not take arguments. Formulas are subject to inheritance, so the same formula can be shared by many objects. 

When a slot is constrained, its formula is executed immediately to compute its value, and it is recorded as being a destination node in the constraint network. Any slots that are read by the formula are recorded as source nodes in the network.  When a source node's value changes, an event is queued to calculate each of its destination nodes.  A slot can be both a destination node and a source node for another slot.  This example continues where the previous one left off, using the object a and its active slot alpha:

% Object construct b
% b slot beta 12
% a formula alpha {expr [b slot beta] * 2}
expr [b slot beta] * 2
% a slotConstrain alpha
attempt to set alpha to 24
1 <-- slotConstrain is returning true, it was successful
% a slot alpha
% b slot beta 21
attempt to set alpha to 42
^-- note a's alpha was set when b's beta changed

% a slot alpha

All the slots accessed within a formula become source nodes in the network, but in some cases the programmer will want to limit this. There are two mechanisms available:

  1. an object can be specified as the constraint limit, only objects that inherit from this object are allowed to participate in the constraint network.  For example, you might create an empty object called Constrainable and then set it as the limit using the command Poet limitConstraints Constrainable.  Then mix Constrainable into only those objects that you want to allow to become source nodes in the network.  This is a global setting that affects all formulas in the program.
  2. Within a formula, you can execute a script as a side-effect with the command Poet sideEffect script.  Any slots accessed inside the script will be ignored.

Poet constraints are one-way, from sources to destinations, but they can be arranged such that there is a circular dependency. It's possible that, when the slots are constrained, an infinite loop will form and Poet will never return to processing user events. The best way to avoid this, other than not creating circular dependencies in the first place, is to call Tcl's update after each invocation of slotConstrain (i.e., don't constrain multiple slots at once).  This will allow the network to run after each new constraint, which should cause it to find a stable state.

Note that all the slots referenced in a formula should already exist. If a slot doesn't exist yet, there's no place to attach the node for the dependency graph, so even if the slot is created later it will not trigger a recalculation. This is either a bug, a limitation, or an unimplemented feature, depending on how you look at it.

Typed Slots

Poet slots can have optional type annotations attached.  Types, like formulas, are named after the slot they qualify and are subject to inheritance.  A type annotation can be any arbitrary string, the programmer is free to use it for any purpose.  Poet slot values are not limited by the type of the slot, but it would be straight-forward to implement objects that did limit slot values according to some type scheme.  Poetics (Tier 3) defines a set of types that enable the object editor to create appropriate editor widgets for an object's slots.  Types are not used internally in tiers 1 and 2.  The programmer may use Poetics' types, implement a different type scheme, or ignore types completely.

This example uses Poetics' type scheme to create four slots: one with any real value, one with an integer value in [0..100], one that can hold one of three values, and one that holds a color.  An editor viewing this object would create different editors for each slot.

Object construct test

test slot price 2.00
test type price <real>

test slot count 4
test type count "<integer> 0 100"

test slot flavor vanilla
test type flavor "<choice> vanilla chocolate strawberry"

test slot color white
test type color <color>