[Next] [Previous] [Up] [Top] [Contents] [Index]

3 Static Types

3.5 Object Role Annotations

Because Cecil is classless, objects are used both as run-time entities and as static, program structure entities. Some objects, such as nil and objects created at run-time through object constructor expressions, are manipulated at run-time and can appear as arguments to messages at run-time. Such concrete objects are required to have all the signatures in their types be supported by corresponding method implementations and all their fields be initialized. In contrast, objects such as cons and list are not directly manipulated at run-time. Instead, they help organize programs, providing repositories for shared methods and defining locations in the type lattice. In return for restricted usage, such abstract objects are not required to have their fields fully initialized nor their signatures fully implemented.

To inform the type checker about the part played by an object, its declaration is prefixed with an object representation role annotation:

rep_role	::=	"abstract"	only inherited from by named objects;
				allowed to be incomplete
	|	"template"	only inherited from or instantiated;
				uninitialized fields allowed
	|	"concrete"	directly usable;
				must be complete and initialized
	|	["dynamic"]	directly usable;
				no static checks

Each of these role annotations appears in the list hierarchy:

abstract object list isa collection;
template representation cons isa list;
concrete representation nil isa list;

Abstract objects are potentially incomplete objects designed to be inherited from and fleshed out by other objects. Abstract objects need not have all their signatures fully implemented nor their fields initialized. For example, the list object is not required to implement the do signature defined for the type list; the implementation of this operation is deferred to children. Because an abstract object may be incomplete, it cannot be used directly at run-time, nor can it appear as a parent in an object constructor declaration. Abstract objects are similar to abstract classes in class-based languages.

Template objects are complete objects suitable for direct "instantiation" by object constructor expressions, but are not allowed to be used directly as a value at run-time. Because new method implementations cannot be specified for anonymous objects, all the signatures specified as part of the type of a template object are required to be fully implemented. For example, the cons object is required to fully implement all list operations, including do. However, because template objects will not be sent messages at run-time, they are not required to have their fields initialized. The cons object is not required to have its head and tail fields initialized. Template objects are analogous to concrete classes in class-based languages.

Concrete objects are complete, initialized objects that can be manipulated at run-time. Like template objects, all signatures must be implemented, and in addition all fields must be initialized, either as part of the field declaration or as part of the object declaration or object constructor expression. Like other named objects, named concrete objects can be inherited from as well. (The child object's role can revert to abstract or template.) Anonymous concrete objects correspond to instances in class-based languages; named concrete objects have no direct analogue and are a feature of Cecil's object model.

If the object role annotation is dynamic or omitted, the object is considered fully manipulable by programs but no static checks for incomplete implementation of signatures or uninitialized fields are performed. (The appropriate checks will be made dynamically, as messages are sent and fields accessed.) Dynamic objects are designed to support exploratory programming, as discussed in section 3.10.

Since object constructor expressions create objects to be used at run-time, neither abstract nor template annotations are allowed on object constructor expressions.

Object role annotations help document explicitly the programmer's intended uses of objects. Other languages provide similar support. C++ indirectly supports differentiating abstract from concrete classes through the use of pure virtual functions and private constructors. Eiffel supports a similar mechanism through its deferred features and classes mechanism. Cecil's abstract annotation is somewhat more flexible than these approaches, since an object can be labeled abstract explicitly, even if it has no abstract methods. Such a declaration can be useful to prevent direct instantiation of the object, perhaps because the method implementations are mutually recursive in a way where subclasses are expected to override at least one of the methods to break the recursion.

In an earlier version of Cecil, a fifth annotation, unique, could be used to document the fact that an object was unique. For example, nil, true, and false all were annotated as unique objects. While the exact semantics of unique was unclear, a plausible interpretation could be that a unique object is like a concrete object except that it could not be used as a parent in an object constructor expression (i.e., it could not be "instantiated" or "copied"). Unique objects could still be inherited from in object declarations, since they might have useful code to be inherited. Unique objects were removed because it was felt that the extra language mechanism was not worthwhile. The template annotation may be removed for a similar reason, since it is not strictly necessary for the type checker, but the distinction between abstract and template objects appears to be useful for documenting the programmer's intentions. The distinction between abstract objects and concrete objects, however, is crucial to being able to write and type-check realistic Cecil code.