HELP FEATURESPEC Aaron Sloman April 1997 Revised 7 Jul 1997 CONTENTS -- Introduction -- Primitive and compound featurespecs -- Example: an array of buttons -- Why create new levels of list structure? -- Featurespecs vs new sub-classes -- Reusing featurespecs -- The slot specifiers -- Working examples -- Introduction ------------------------------------------------------- A featurespec is an extendable structure that is used by the procedure interpret_specs to modify an objectclass instance which has been created with default slot values. The purpose of featurespecs is to allow instances of objectclasses to be created in contexts where the default slot values are to be varied, and where it would be tedious or inefficient to create special subclasses to handle all the special cases. Featurespecs are "extendable" in the sense that they can either be primitive or complex, and either type can be combined to form larger complex featurespecs. For example, in an instruction to construct a large collection of instances a primitive featurespec may specify a new default for the whole collection, and additional featurespecs may be added to that for lower level subsets. By the time each actual individual instance is created, the top level featurespec may have had several additional featurespecs combined with it at different levels in the subset hierarchy. Examples are given below. -- Primitive and compound featurespecs -------------------------------- A primitive featurespec is an even length vector of slot/value items, where each slot is represented by its accessor procedure or the name of the accessor procedure (so that its valof is the procedure). The interpreter is given an object (usually a newly created object) and a featurespec and interprets the featurespec by overwriting the relevant slots in the instance with the corresponding values. Compare objectclass REF * SET_SLOTS A featurespec is extendable in that two or more primitive or compound featurespecs can be combined in a list to create a new compound featurespec. When a compound featurespec is interpreted, the interpreter walks through the tree from left to right, interpreting the primitive featurespecs in the order in which they occur in the list, and calling itself recursively when it finds compound featurespecs. Featurespec (FS): Primitive featurespec||Compound featurespec Primitive featurespec: {Slot1 Val1 Slot2 Val2 .... Slotn Valn} Compound featurespec: [FS1 FS2 FS3 ....] For convenience we also allow a featurespec to be FALSE, meaning no change is required. -- Example: an array of buttons --------------------------------------- For example, suppose you have various classes of buttons for which defaults have been defined, e.g. default values for the font for the button label, the colour for the button label, the background colour, the colour of the border, the colour the border changes to temporarily when the button is selected ("pressed"), the height, the width, etc. Now suppose you are defining a procedure create_button_array, to create an array of such buttons and for these buttons you wish to allow some feature specifications that override some of the defaults (e.g. different font and font colour). The procedure therefore takes a list of button row descriptions and a featurespec indicating new defaults for the whole array. The featurespec will be handed down to the lower level procedures, e.g. a procedure create_button_row, for creating each row of buttons. Now suppose that for some of the rows you want additional features to be specified, e.g. a new border colour. A feature specification can be included as the first part of each row description. When create_button_array finds such a featurespec it can form a new compound featurespec containing the original featurespec it was given and the new featurespec. It then gives the combined featurespec to create_button_row. This procedure creates a row containing individual buttons from a list of button descriptions. It does this by calling a create button procedure for each description, handing on its current featurespec. However, some of these individual descriptions may also include featurespecs either adding new features or changing the defaults in previous featurespecs. So, before giving the button description to the button creation procedure, the row creation procedure first takes whatever featurespec it was given, appends the new one for the button and gives the new combined featurespec to the button creator. This takes the button description, creates the button, then uses the featurespec interpreter to interpret the featurespec (in this case a compound featurespec) to update some of the slots of the button instance, then draws the button on the screen. E.g. given 1. Featurespec for the whole array: {font '8x13' stringcolour 'yellow'} 2. Featurespec for one of the rows {border 6 bordercolour 'pink'} 3. Featurespec for a certain button in that row: {font '10x20' border 8 stringcolour 'ivory'} The first vector is given to create_button_array. When it finds the second vector at the beginning of a row description it combines the tow and gives create_button_row a featurespec in the form of a list containing two vectors: [{font '8x13' stringcolour 'yellow'} {border 6 bordercolour 'pink'} ] When create_button_row finds that one of its button descriptions has the third primitive featurespec listed above, it creates a new compound featurespec to give to create_button, i.e. a two element list containing the above and the third primitive featurespec, i.e.: [ [{font '8x13' stringcolour 'yellow'} {border 6 bordercolour 'pink'} ] {font '10x20' border 8 stringcolour 'ivory'} ] NOTE: The odd numbered elements of a primitive featurespec vector can be either the actual procedures or the words that name them. If you don't want to export the names, use the procedures themselves, by preceding the name with "^", e.g. {^font '10x20' ^border 8 ^stringcolour 'ivory'} NB These are hypothetical slot names, and will not necessarily work with RCLIB libraries. -- Why create new levels of list structure? --------------------------- Why not just concatenate featurespecs by forming a longer list each time, adding new features on the right? The reason is simply that appending to the right of a list generally creates "garbage" since the list has to be copied first. By allowing a procedure to create lists that contain existing lists as elements without copying them the procedure that creates a new list can also return it to the store manager (using sys_grbg_list). Although the extra levels of nesting may make the compound featurespecs look more complex, the procedure interpret_specs is very simple. define interpret_specs(obj, specs); returnunless(specs); ;;; If false, do nothing if isvector(specs) then ;;; It is a bottom level featurespec, so interpret it directly lvars list = specs.destvector.conslist; set_spec_slots(obj, list); ;;; reclaim free space sys_grbg_list(list); 0 -> list; ;;; for uncleared alpha registers... elseif islist(specs) then ;;; Interpret the featurespecs in the list in left to right order lvars sub_specs; for sub_specs in specs do interpret_specs(obj, sub_specs) endfor else mishap('Object specs should be list, vector or false', [^specs]); endif enddefine; The procedure set_spec_slots is similar to the standard objectclass procedure set_slots for updating instances. See REF * OBJECTCLASS, except that (a) it is restricted to lists and (b) it allows slot names instead of the procedures to be used. -- Featurespecs vs new sub-classes ------------------------------------ It would be possible to avoid the creation and use of featurespecs by defining new subclasses of buttons (or whatever) for each combination of slot values required and simply specify which class is to be instantiated. Since many different combinations may be needed in some application, and since each particular combinations may be used only once or twice, the overhead of defining new classes could be wasteful. The use of featurespecs provides a simple and effective alternative, and since they are reusable they are a bit like classes. It is also possible to use featurespecs to reuse instances! E.g. a collection of instances used for one purpose may have their style or other features changed by using the interpreter to change each of them in accordance with a featurespec. Thus a collection of buttons could first have one style of presentation then another, perhaps to indicate that the context has changed. If different subclasses were used, new instances would have to be created, since in Objectclass instances cannot change their class membership after creation. -- Reusing featurespecs ----------------------------------------------- If certain features are to be used several times they can be put into a featurespec, and then that featurespec can later be combined with others to form different compound featurespecs. Some of the compound featurespecs may also be reused. This facility allows different reusable "styles" of presentation (or other kinds of style) to be defined and used in different contexts. Thus one style might be used for all panels containing "help" buttons, another for panels containing buttons for controlling the values of global variables, another style for panels containing action buttons, and so on. A problem with this approach is that you can easily have a reusable featurespec that refers to slots that are not appropriate for some of the classes whose instances can turn up when the featurespec is used. This is solved as follows. Slot-accessors in objectclass are simply methods, so we can define some "dummy" slot-updater methods for the top level class which do nothing when applied to instances of classes to which they are irrelevant. Then it is always safe to apply them to instances of any of the subclasses, even though they do nothing. For instance in LIB RC_BUTTONS, toggle buttons and counter buttons have no rc_button_pressedcolour field, which is used by action buttons to change their border colour when a button is "pressed". So the library has defined a dummy updater method for the top level category rc_button: define :method updaterof rc_button_pressedcolour(x, pic:rc_button); ;;; do nothing enddefine; This means that attempting to update the pressedcolour slot of any instance of a class in the rc_button hierarchy that does not include this slot, will simply have no effect, and no error will be generated, as would otherwise occur when attempting to apply a featurespec {^rc_button_pressedcolour 'red'} to an instance of rc_toggle_button. Thus a row of buttons with this featurespec can safely include the toggle button along with action buttons, and interpret_specs will not complain. -- The slot specifiers ------------------------------------------------ In the examples above the slots were represented in primitive featurespecs by words, e.g. {font '8x13' stringcolour 'yellow'} However in a program the words would have to be actual names of procedures, which either are slot accessing procedures, or procedures which have updaters that do something sensible. In either case you have the choice of using either the procedure or its name (if suitably exported) as the slot specifier, i.e. both of these will work {^rc_button_font '8x13' ^rc_button_stringcolour 'yellow'} {rc_button_font '8x13' rc_button_stringcolour 'yellow'} This syntax can become quite cumbersome when long fieldnames are used (which is normally good style). If necessary, macros or new syntax words could be used to define a more compact notation. -- Working examples --------------------------------------------------- Working examples can be found in HELP * RC_BUTTONS LIB * RC_POLYPANEL (search for '{spec' See also HELP * RCLIB, TEACH * RC_LINEPC --- $poplocal/local/rclib/help/featurespec --- Copyright University of Birmingham 1997. All rights reserved. ------