A Prototypal Perspective

The TADS 3 object model is not class-based. By this we mean that in a traditional class-based model classes are used to provide the blueprints from which instances are derived that do the actual work of the program. Classes do not generally receive messages except to create new objects. The purpose of the class is to provide the blueprint for a new object.

 

Inheritance

TADS adopts a distributed inheritance scheme. This means that an objects definition consists only of those properties that it directly defines. The other properties that constitute the state and behavior of the object, and which are inherited from other objects, are not part of the object definition, but are determined through a traversal of the object’s inheritance hierarchy.

 

To explore this further, let’s examine the following representation of an object’s definition:

 

<objectName>: classList

{

       <directlyDefinedPropertyList>

}

 

The <> symbols indicate that these elements are optional. Only the class list is required in an object definition.

 

Traversing the Inheritance Hierarchy

Inheritance in TADS 3 moves down the inheritance tree of a trait, and then across to the next trait in the class list, from left to right, iterating down through each inherited trait in this fashion until we have either found the property being evaluated or have reached the end of the hierarchy.

 

 

 

 

 

 

 

 

 

Complex as this process seems, it is an automatic one, and the author need not trouble himself with its functions, but merely understand that inheritance is fully distributed along the inheritance hierarchy of the object, and only terminates when we encounter the property being invoked, or reach the end of the inheritance hierarchy.

 

PropNotDefined

If the property is not encountered by the end of the inheritance traversal, then the VM may send obj1 the message propNotDefined(), if the object defines this method. propNotDefined() is the last final chance that an object gets to handle a message.


Types of Objects

Classes

In TADS a class is an object that has been defined using the keyword class. This object is like any other symbolically-referenced object, except that the intrinsic method isClass() it inherits from Object return true. Objects not defined with the class keyword will return nil when sent the isClass() message.

 

The chief impact in distinguishing classes from other objects is that these objects are excluded from the parsing process.

 

Traits

TADS refers to the objects that other objects derive from as superclasses. But inheritance isn’t limited to classes. An object can inherit from any TadsObject class object or class. TADS 3 provides both the new operator and the createInstance() message for dynamically creating an instance of an object. Proteus therefore adopts the term trait, taking on a more prototype-based perspective, in examining the TADS object model.

 

Parents

The TADS 3 getSuperclassList() message is defined for all objects deriving from Object. It provides a list of those traits directly defined in the object definition. For instance, the following object definition:

 

       myObject: Fixed, Surface;

 

produces the superclass list [Fixed, Surface]. Proteus duplicates and extends this functionality by providing the getParentList() message. We can also determine if an object is a parent of another object by use of the isParentOf() method.

 

Children

In a prototypal perspective, derivations of an object are called children. In TADS 3 these children are created using either the new operator for classes, or the messages createInstance()or createClone() for objects. We can retrieve a list of an object’s children by using the getChildList(). We can also interrogate an object to determine if it is a child of another object by using the isChildOf() method.

 

The new Operator vs. createInstance()

From a technical standpoint there is no difference between using the new operator as opposed to the createInstance() message, except that the latter

 

“…performs a recursive VM invocation on the constructor whereas 'new' invokes the constructor directly, but that's an internal detail that should be of no practical interest whatsoever to the code (the only way the code can tell the difference is to look at the stack traceback available through reflection).” (MJR).

 

While new can only be used with objects that have been designated as classes, there is no technical difference in using createInstance() on an object that has been designated as a class and one that has not.

 

Intriguing Aspects of TADS Object Model

 

Changes to a Trait’s State Reflected in Children

Children in TADS 3 have their own peculiarities. Any changes of state made to an object’s traits during run-time are immediately reflected in the inheritance hierarchy of their children.

 

To demonstrate this, we can observe what happens when we define a simple object deriving from Thing class:

 

myObject: Thing;

 

If now we were to change the value of an attribute of Thing class, such as:

 

Thing.isHim = true;

 

We then find that every child of Thing inherits this change in attribute.

 

Modifying Attributes at Runtime

Because TADS inheritance model is a distributed one, changing an object’s state automatically adds the changed attribute to the object’s directly-defined property list. Further modification merely changes the value assigned to the attribute.

 

If we define myObject as follows:

 

myObject: Thing;

 

And at some point during run-time we change the state of the object, say by setting &isHim to true:

 

myObject.isHim = true;

 

Then the internal object definition for myObject changes to:

 

myObject: Thing

{

       isHim = true

}

 

Once an attribute has been added to an object’s directly-defined property list it cannot be removed. For instance, setting the &isHim attribute to nil does not remove it from myObject’s directly-defined property list, but merely changes the property’s assigned value to nil.

 

myObject.isHim = nil;

 

We now have:

 

myObject: Thing

{

       isHim = nil

}

Adding Attributes at Runtime

By the same token, it’s perfectly feasible to assign an attribute to an object, even when the attribute does not appear in the object’s inheritance hierarchy. The new attribute is added to the object’s directly-defined property list in a fashion identical to that of modification cited above.

 

Object Derivations

The TADS 3 language provides the ofKind() message to determine if an object either inherits from a trait or is the object passed as the message argument. For instance, the statement:

 

Thing.ofKind(Thing)

 

returns true. This is in contrast with the TADS 2 language’s isclass() function, which would have returned nil in the case of comparing the TADS 2 thing class to itself. The TADS 3 ofKind() message is very useful in that it more fully embraces the prototypal perspective of the object model.

 

isDescendantOf(obj)

Proteus does provide a mechanism similar to the TADS 2 isclass() function. The message isDescendantOf(obj) can be sent to any Object class object. It will return true if the object derives from the object, but nil if the object == obj or does not derive from it.

 

getDescendantList([args])

This message is available for any Object class object and returns a list of all descendants of this object. The message takes a variable length list as its sole argument. If no argument is passed, then object classes and instances are returned in the list. The arguments should be the same flags passed to the firstObj() / nextObj() mechanism, which the method employs in order to build the list.

 

If no objects are derived from the object then the message returns an empty list.

 

getChildList([args])

Like the proceeding message, this one returns a list of objects derived from the object, but only if the lineage is immediate. An object is considered a child of another object if its getSuperclassList() contains the other object.

 

Suppose for instance, that we define the following relationships:

 

myObject: Thing;

 

myChildObject: myObject;

 

Only myObject is said to be a child of Thing class, as it will list Thing as an element of its superclass list. myChildObject, while a descendant of Thing, is not a child , as it will list only myObject in its superclass list.

 

The distinction provided by child is that it provides a limited list of those objects whose states and behaviors are most closely related to, being immediately derived from, the object under examination. We can, for instance, acquire a list of all children of BasicLocation in the library using the @list child sym metacommand.

 

>@list child sym BasicLocation

§          class NestedRoom

§          class tads#433 ( 481 *Room*)

 

 

We can then draw the relationships between BasicLocation, NestedRoom, and tads#433 (an unnamed internal object represented by ‘ 481’ in the global symbol table). We could further trace the chain of derivation from NestedRoom:

 

>@list child sym NestedRoom

§          class HighNestedRoom

§          class tads#377 ( 3ac *Vehicle*)

§          class tads#454 ( 4ab *BasicChair*)

 

 

From this analysis we can see that HighNestedRoom is derived from NestedRoom, which in turn is derived from BasicLocation.

 

isDirectLineageOfKind(trait)

This message returns true if the object is ofKind(trait) and the lineage isn’t obscured by intervening multiple inheritance structures that might radically affect the state or behavior of the object.

 

Suppose we define the following object relationships:

 

myObject: Thing;

myChildObject: myObject;

 

myOtherObject: Fixed, Thing;

 

And we ask the question, which of these objects derive from Thing, and only from Thing, or is the Thing class itself? The answer would be: Thing and myObject. But myChildObject derives solely from myObject, and therefore is closely related to Thing, more so than myOtherObject, which derives from both Fixed and Thing.

 

It’s a question of object genealogy of a sort, and myChildObject.isDirectLineageOfKind(Thing) will return true; while myOtherObject.isDirectLineageOfKind(Thing) will return nil. A nil response indicates that the object being examined either does not derive from the trait, or does not derive solely from the trait.

 

First Ancestors

Ancestors play an important part in the naming of an object in Proteus when an object doesn’t possess a symbolic object name. These objects are either dynamically-created or anonymously-defined, and displaying their object names using only object reference tags is too cryptic, so Proteus incorporates ancestor information into the name of the object.

 

Proteus provides the message getFirstAncestorList() for Object class objects.

 

First Symbol Ancestors

But an ancestor may have been dynamically-created or anonymously-defined, which means that we would only compound the cryptic nature of naming the object using object reference tags. So Proteus works up the inheritance tree to find an ancestor that exists in the global symbol table. These objects were either statically-defined in library or author code, or dynamically-generated unnamed internal objects.

 

Proteus provides the message getFirstSymAncestorList() for Object class objects.

 

First Named Ancestors

For dynamically-created or anonymously-defined objects Proteus uses the first named ancestors to represent the object, in conjunction with an object reference tag. First named ancestors are the first ancestors encountered in traversing the inheritance hierarchy, where the trait is not dynamically-created, anonymously-defined, or an unnamed internal.

 

Proteus provides the message getFirstNamedAncestorList() for Object class objects.

 

First Descendants

Proteus provides the message getFirstDescendantList() for Object class objects. Without passing an argument the message returns the same information as getChildList(), but provides a means of iterating down the lineage of parent / children for an object.

 

First Named Descendants

Since unnamed internals are the result of modifications of object definitions, it is difficult to tell what named object the unnamed internal’s code once referred to. For dynamically-created or anonymously-defined objects an empty list is returned. For all other objects this is the same as getChildList().

 

Proteus provides the message getFirstNamedDescendantList() for Object class objects.

 

The ofKindOrUU(obj) Method

This method is similar to the ofKind() intrinsic method in that it returns true if the this object is ofKind(obj). Otherwise it checks to see if obj is one of this object’s modifications. If so, then the method returns true. Otherwise the method returns nil.

 

The propDesc(prop) Method

The following Table describes the behavior of static and non-static methods

 

Method

Static

Non-static

Not Defined For Object

getPropList()

Returned

Returned

Not Returned

getPropParams(prop)

Determined by method

[0,0,nil]

[0,0,nil]

propDefined(prop, flags?)

Determined by method

nil

nil

propType(prop)

Determined by method

nil

nil

 

In fact, getPropParams(prop) behaves exactly the same for non-static properties as for properties not defined on the object.

 

Now, for List, &subset is a non-static property. It will be returned by List.getPropList(), but both propDefined(prop, flags?) and propType(prop) will return nil.

 

For an instance of List, &subset is a static property. It will be returned by list.getPropList() with the following values.

 

 

And list.propType(prop) will return TypeNativeCode

 

List.subset() has no meaning, while list.subset() does. The propDesc() method provides us with exactly the same information for static properties that propType() and propDefined() provide. This is true as well for object’s that do not define or inherit the property. The propDesc() method helps us to differentiate between objects that define the property non-statically and those that do not define the property at all.

 

The propDesc(prop) method returns the following information for List.propDesc(&subset, flag) and  list.propDesc(&subset, flag), as well as Object.propDesc(&subset, flag):

 

Flag

Description

List

list

Object

PropDescType

Property Type

TypePropNonStatic

TypeNativeCode

nil

PropDescDefined

Property Defined Flag

PropDefNonStatic

PropDefInherits

PropDefNotDefined

PropDescGetClass

Property Defining Class

List

List

nil

PropDescInfo

A list of info about this property

[TypePropNonStatic, PropDefNonStatic, List]

[TypeNativeCode, PropDefInherits, List]

[nil, PropDefNotDefined, nil]

 

 

 

This file is part of the TADS 3 Proteus Library Extension

Copyright ©  2001-2004 Kevin Forchione. All rights reserved.