!topic LSN on Tagged Types and Object-Oriented Programming in Ada 9X !key LSN-1045 on Tagged Types and OOP in Ada 9X !reference MS-3.4.1;4.6 tagged types and type extensions !reference MS-4.6;4.0 constructor conversions !reference MS-4.3.2;4.6 extension aggregates !from Tucker Taft $Date: 92/10/18 17:20:42 $ $Revision: 1.3 $ !discussion This Language Study Note discusses the OOP features of Ada 9X that relate to tagged types. Although there are other features that support OO concepts (such as "package extension" via child packages, formal packages, formal derived types), we will focus on the "classic" features of OOP relating to inheritance and polymorphism. This LSN is fairly wide-ranging, because trying to cover a single syntax or semantics issue in isolation can miss the important integration and consistency issues. Furthermore, we want to discuss the fundamental model for OOP in Ada 9X, and repeating such a discussion in multiple LSNs would just be confusing. We will try to use capitalized headings to help the reader quickly find the section of most interest to him/her. FUNDAMENTAL MODEL OF OOP IN Ada 9X When considering how to add "true" OOP to Ada 9X, we were faced with a number of possible models on which to base the approach. Here is a quick summary of some preexisting OOLs: Key: SI = Single inheritance, MI = Multiple Inheritance Ref = By-reference assignment, Val = By-Value assignment GC = Garbage Collection Simula 67 Type = Module, SI, No Metaclasses, static method lookup, Ref&GC SmallTalk80 Type = Module, SI, Metaclasses, dynamic method lookup, Ref&GC C++ Type = Module, MI, No Metaclasses, static method lookup, Val ObjectPascal Type /= Module, SI, No Metaclasses, static method lookup, Ref Objective C Type = Module?, SI, Metaclasses??, dynamic method lookup, Val?? Eiffel Type = Module, MI, No Metaclasses, static method lookup, Ref&GC CLOS Type /= Module, MI, Metaclasses, dynamic method lookup, Ref&GC Modula3 Type /= Module, SI, No Metaclasses, static method lookup, Ref&GC (My information on Objective C is pretty sketchy.) If we were to adopt a type = module approach, we would presumably have to add a new kind of type, either a "package" type (whatever that would turn out to be), or something like the "class" construct of C++, Eiffel, and Simula 67. The package-type = module approach sounds initially appealing, because a package is already a module. However, all types in Ada 83 have "derivable subprograms" and support derivation. A "derivable" subprogram of a type is one declared immediately in the same package visible part as the type, having a formal parameter or result of the type. But with the type = module approach, the "interesting" operations are the ones declared inside the module, which take an instance of the type as an implicit (prefix) parameter. We would then have operations associated closely with the type that are both "inside" it and "outside" it -- the type = module model is broken. Furthermore, package types bring along a large number of other difficult semantic issues. What about subpackages, types, exceptions, etc., declared inside such a package type? Ada 83 carefully avoids having one type declared inside another. If we allowed nested type declarations, what happens if the inner type depends on some component of the enclosing package type? This means that each instance of the package type has its own nested type. Alternatively we must disallow nested types, or require that nested type declarations not depend on any components of the enclosing type. Similar considerations arise for nested exceptions. Does each instance of a package type have its own set of exceptions (like an instance of a generic package), or do they all share the same set? Finally, we would have to address the issues of binary operations. In SmallTalk, binary operations are asymmetrical, with the left operand being the "receiver" and the right operand treated as a parameter. In C++, both the asymmetrical approach is supported, as well as "friend" operators, which are symmetrical but not inherited. Ada 83 already has an elegant solution for all of the above problems. Types and modules are separate things. Types and their operations are declared together in a module, and binary operations can be symmetrical while still inherited by derived types, using the "systematic substitution" rule. Given an early recognition of the difficulties of "type = module" in the context of Ada, and a recognition of the excellent basis for inheritance provided by the derived type concept of Ada 83, we chose to build on derived types, and abandon the "type = module" approach used by some OOPLs. As alluded to above, if we created a separate inheritance mechanism specific to package types, we would still have to deal with the general derived type mechanism. So rather than having two, weaker, and potentially conflicting inheritance mechanisms, we chose to strengthen the existing derived type mechanism. Therefore, an important principle of the Ada 9X approach to OOP has been to build on the Ada 83 derived type mechanism. Whenever possible, we use the same basic syntax, the same basic concepts, and the same basic semantics to provide OOP. We believe this will ensure consistency throughout the language, give existing Ada 83 programmers a leg up in understanding Ada 9X, and help to minimize the apparent size and complexity of the language. We identified two basic things missing from the derived type mechanism provided by Ada 83. First of all, there was no way to add additional components as part of type derivation. In many situations, it is important to be able to add components to support the new or overridden operations of a derived type. Secondly, there was no way to write code where the "underlying" type of the objects being manipulated was not known at compile-time. That is, there was no support for "run-time polymorphism." The "compile-time polymorphism" provided by generics, and the "ad hoc" polymorphism provided by overloading is of course very useful. However, there are many paradigms where run-time polymorphism provides significant advantages in terms of flexibility and reconfigurability. Variant records are about the only alternative, and these and the associated case statements are notoriously painful to maintain when the number of variants is increasing over time. We have addressed these two issues directly and straightforwardly. We found that there was no need to invent totally new constructs or radically different kinds of types. Record and private types provide an excellent base on which to support type extension, and the preexisting Ada 83 concept of a "class of types" provides an excellent basis for defining the semantics of run-time polymorphism. TYPE EXTENSION Based on experience with Ada 83, C++, SmallTalk, other OOPLs, and the X-Windows Toolkit (Xtk) we recognized the importance of having both visible record types and private types, as well as being able to extend a visible record type with a private extension part, and vice-versa. We therefore adopted a simple syntax based on the original Ada 83 syntax for derived types plus that for type definition. Where Ada 83 had, roughly: type T is ; -- for defining a new type and type NT is new T; -- for defining a derived type we have proposed for type extension, roughly: type NT is new T with ; In particular, given as either: record end record or private we get: type NT is new T with record end record; or type NT is new T with private; We originally considered type extension/specialization for essentially any kind of type, but have (at least for Ada 9X purposes!) restricted ourselves to record and private extensions for now. However, it is useful to note that the syntax "new T with" can be inserted in essentially any type definition to support extension/specialization. For example: type NT is new T with delta 0.1; might be a way to provide a "reduced accuracy" derived type (given that reduced accuracy subtypes introduces certain undesirable anomalies). Similarly: task type NT is new T with entry E3(P1 : Integer); end NT; might be a way to add an entry to a task as part of a derivation. For now, we leave such things (somewhat reluctantly) to the Ada200X and beyond crowd (or the GNU Ada tinkerers). As a side note: "with limited private" is no longer permitted. Originally, in addition to allowing "with private" to define a "private extension," we allowed or required "with limited private" for defining a "limited private extension." However, this turned out to be redundant and/or confusing, since it implied that one could define a limited private extension T2 of a non-limited type T via "type T2 is new T with limited private;". It turns out that this would not work, because T, and hence T'CLASS, both allow "=" and ":=", but T2, a type in the class rooted at T and potentially represented by a formal parameter of type T'CLASS, does not. Dropping operations is not permitted in a (non-abstract) derived type. Even without using T'CLASS, dropping operations in a derived type would cause contract model violations for a formal derived type, since they are assumed to have all of the operations of the formal parent. Given this, a type extension is and must be limited if and only if its parent type is limited, so the simpler and less redundant syntax "with private" is sufficient. CLASS-WIDE PROGRAMMING An important feature of Ada 9X (and Ada 83) is that a derived type, whether or not it is an extension, is a distinct type from the parent type. Strong type checking prevents any inappropriate mixing of the two types. They share components and operations, but are mixable only through explicit conversion. In this important sense (and others), derived types in Ada 9X and Ada 83 are not like "subclasses" as the term is used in object-oriented programming languages. A subprogram that expects a parameter of type T may not be passed a parameter of type T2, even if T2 is derived from T, without an explicit conversion. However, subclassing is the basis for an important object-oriented programming paradigm, where one can write a single algorithm that operates on any object of a class or of any of its subclasses. This is kind of like a generic, but where the instantiation is implicit and happens at run-time. We call this paradigm "class-wide" programming, though one could also call it "generic" programming (if that term weren't already taken), or one could use the term "run-time polymorphism" (which perhaps more properly refers to the mechanism rather than to the paradigm). We support class-wide programming in Ada 9X by generalizing the preexisting Ada 83 concept of a "type class" to include the set of types defined by a given "root" type and all of its derivatives (direct and indirect), a so-called "derivation class." We allow a formal parameter to be of the "class-wide type" T'CLASS, thereby indicating that the actual parameter may be any type in the class, including any "subclass" such as T2'CLASS where T2 is some derivative of T. Similarly, we allow the designated type of an access type to be T'CLASS, allowing values of the type to designate objects of any types in the class rooted at T. EXTENSION AGGREGATES In Ada 83, explicit conversion is permitted both from a derived type to its parent type, and vice versa. However, if the derived type has more components than the parent, there is a question about how conversion from the parent to the derived type should be performed. One important consideration is that conversion is permitted on IN OUT and OUT parameters in Ada. This means that conversion is not always "constructing" a new value, but rather sometimes simply "viewing" it as a different type. This is particularly relevant for limited types, since constructing new values of a limited type implies copying, which is intended to be restricted for limited types. However, conversion from a parent to a type extension clearly has to construct a new value, even if the new components are implicitly initialized. We solved this problem in 9X initially by simply declaring certain conversions as "constructor conversions," and by augmenting the syntax to allow the additional components of the target type to be specified explicitly as part of the conversion. Constructor conversions were not allowed as IN OUT or OUT parameters. Unfortunately, there is the special case where no components are being added, but nevertheless a distinct type tag is assigned, requiring the construction of a new value so that it can carry the new tag. This meant that a constructor conversion might be syntactically indistinguishable from a normal "view" conversion, leading to some delicate semantics and potential generic contract model violations (if such a conversion appeared in a generic body as an OUT parameter, and for one case it was legal, but for another it was illegal since it was considered a constructor conversion, we would have a contract model violation). On a suggestion from some reviewers, we decided to treat constructor conversions as a special kind of aggregate, rather than as a special kind of conversion. This new kind of aggregate is called an "extension aggregate" with the following syntax (upper case = reserved-word): (expr WITH extension_part_subaggregate) Given a record extension such as: type T2(D : Integer) is new T with record F, G : Integer; S : String(1..D); end record; an extension aggregate to take a value X of type T and produce a value of type T2 would be as follows: (X with (D => 3, F|G => 0, S => "abc")) The extension_part_subaggregate is written as a record aggregate (analogous to the subaggregates used in multidimensional array aggregates). If you were to erase the "new T with" from the declaration of T2, the subaggregate would correspond exactly to the record aggregate necessary to create a value of such a non-extension T2 from scratch. In other words, the discriminants if specified for T2 and the components that are added for T2 are specified in the subaggregate. Positional notation may also be used, by simply ignoring any components inherited from X in the position numbering. A fully "named" notation may be provided to enhance maintainability, by qualifying the "parent" expression, as follows: (T'(X) with (D => 3, F|G => 0, S => "abc")) Various reviewers suggested that some support should be given for directly "converting" from a more distant ancestor type than the immediate parent. Initially we were concerned that the semantics might be too hard to define, but this turned out not to be the case. Furthermore, if the immediate parent is abstract, while a more distant ancestor is non-abstract, then such a "multi-level" extension aggregate would be essential. Hence, the semantics as given in ILS version 1.0 allows the "parent" expression to actually be of any "ancestor" type, and the subaggregate must specify a value for every component of the resulting composite value that is not inherited from the given ancestor type. Qualifying the "ancestor" expression as illustrated above can be used to resolve any ambiguity. The extra level of parentheses associated with the subaggregate are necessary to handle the special case where the extension turns out to inherit all of its components from the ancestor. For this case, we provide the syntax: (X with null) Without the extra level of parentheses, this would be ambiguous with an extension that had only one additional component, which was of an access type. Other syntaxes were suggested for extension aggregates. In particular, rather than using "with" to separate the ancestor expression from the other components, it would be possible to use some special "name" for the ancestor "part." For example: (T'PART => X, D => 3, F|G => 0, S => "abc") This approach tends to eliminate positional notation, or introduces a new category of ambiguity into the resolution of record aggregates (since the "first" aggregate component might be of any ancestor type, or correspond to the "true" first discriminant/component of the type). Furthermore, T'PART is an oddball beast, since it does not denote anything, and is only useful as a name in an extension aggregate (essentially making it special syntax). Conceivably T'PART might be usable as a way to extract the "T" part of a value (e.g. X := T'PART(Y);), but simply "T" already does that (e.g. X := T(Y);). Finally, this special name approach obscures the fact that a significantly different kind of operation is being performed than a normal record aggregate. The presence of the reserved word "with" clearly signals the fact that this is an extension aggregate, and nicely echoes the syntax of type extension. NULL EXTENSIONS One side effect of having a distinct syntax for constructing a value of a derived type given a value of a parent tagged type, is that it becomes necessary to know in a generic whether a formal type and a derivative of it declared in the generic spec are in fact tagged or not. For example: generic type T is limited private; package GP is type NT is limited private; function Convert(X : T) return NT; private type NT is new T; end GP; package body GP is function Convert(X : T) return NT is begin return NT(X); -- or (X with null)??? end Convert; end GP; Our general semantic model is that generic specs are macro expanded, but generic bodies need not be, and bodies are either semantically correct or incorrect independent of the actual instantiation. If the actual type for T is in fact a tagged type, then the conversion NT(X) would be illegal. We considered various solutions to this problem: a) treat NT(X) as equivalent to (X with null) when "appropriate." Unfortunately, this doesn't solve the problem, since if the NT(X) appeared as an OUT parameter in the generic body, it would have to be illegal if the actual type were tagged and limited, since it would require at a minimum a change in the tag, which would in general require copying the object X, something not supported for limited types (since they might be aliased, contain protected objects, etc.). Furthermore, aggregates are not permitted for limited types, so interpreting NT(X) as (X with null) would not work for a limited type in any case. b) disallow tagged types as actuals if the formal is not tagged. This would seriously limit the flexibility of generics. In particular, "general" generics like SEQUENTIAL_IO, UNCHECKED_DEALLOCATION, UNCHECKED_CONVERSION, etc., could not use "private" or "limited private" as the "most general" formal type. One would need an UNCHECKED_TAGGED_DEALLOCATION, etc. c) disallow "type NT is new T;" when T is tagged. This is the alternative we have chosen. This works because generic specs are rechecked on instantiations, whereas generic bodies treat T according to its "formal" characteristics, meaning that it is *not* tagged within the generic body, and derivatives don't get their own tag, don't get dispatching, etc. That's OK since these derivatives aren't visible outside the generic body anyway, and it's essential to avoid more subtle contract model violations in the body. Hence, to echo the syntax of extension aggregates where no new components are added, we have proposed the syntax: type NT is new T with null; as equivalent to, but more readable than: type NT is new T with record null; end record; This ensures that all tagged types have either "tagged" or "new T with" in their declarations. In working examples, we have found it is not that uncommon to have extensions that only need a new discriminant part, or a new set of dispatching operations, but no other new components. Therefore, we believe that this null extension part is useful, and makes the requirement that every derivative of a tagged type be syntactically an extension more palatable. To go back to our generic example, the generic as written would fail at instantiation time if T were tagged, because "type NT is new T;" (which appears in the spec) would then be illegal. To allow this generic to work on all types, rather than using type derivation here (it is not doing much in this example), making NT a record containing a single component of type T would work: generic type T is limited private; with procedure Assign(Left : out T; Right : in T) is <>; -- we need this since we need to copy as part of Convert, -- in case T is tagged. package GP is . . . -- as above private type NT is record X : T; end record; end GP; package body GP is function Convert(X : T) return NT is Result : NT; begin Assign(NT.X, X); return Result; end Convert; end GP; Alternatively, one could leave the generic the way it was, and simply accept the fact that it would not be usable on tagged types. Of course, a generic that declares a type derived from its formal type is pretty rare, though in 9X it may be more common, but there it will explicitly require a tagged type (or perhaps some extension of a specified tagged type), so this issue doesn't arise. Similar considerations apply to normal private types whose full type turns out to be tagged. Non-extension derivatives of such a private type are permitted, but these are not tagged types, and are not assigned their own tag. Given the above, it is effectively possible for both "type NT is new T;" and "type NT2 is new T with null;" to be legal for the "same" T, though the first derivation must be in a place where T appears untagged (e.g. in a generic body or outside the scope of the full type declaration), while the other must be in a place where T is visibly tagged (e.g. outside the generic, or within the scope of the full type declaration). The essential difference between these two derivations is that the one without "with null" does not assign a new tag to NT, while the one with "with null" does assign a new tag to NT2. One can freely convert back and forth between T and NT, whereas one can only go from T to NT2 using an extension aggregate using "with null." There are other obvious ramifications as well since if a value of NT is (indirectly) converted to T'CLASS, it behaves indistinguishably from a value of T. On the other hand, a value of NT2 upon conversion to T'CLASS "brings along" its own tag and its own set of dispatching operations. Hence, we are reserving "type NT is new T;" for the case where the values of the two types are semantically indistinguishable at run-time (of course, they might have different physical representations). On the other hand, "type NT is new T with ..." indicates that the values of NT *are* semantically distinguishable from T at run-time, if only by their tag, and while conversion from NT to T is always permitted, going from T to NT requires an extension aggregate, and constructs a "new" value. We could go so far as to allow "type NT is new T;" even for visibly tagged types, but it would seem to be too error prone, since simply omitting the "with null" would result in a legal program with potentially different semantics. However, preserving the flexibility of generics and private types argues for allowing a tagged type to "appear" untagged in certain circumstances, and in these places "type NT is new T;" is the only thing that is legal, with the semantics as explained above (i.e. no new tag is assigned; types are interconvertible). To summarize: We have proposed a new syntax for "null extensions" where only a new set of discriminants or a new tag is required, using the words "with null". The net effect is that all derivatives of a (visibly) tagged type are extensions, which has eliminated a number of special cases, and is consistent with our use of an extension aggregate rather than a normal type conversion to convert from a tagged type to one of its derivatives. USE OF THE WORD "CLASS" In Ada 9X, we use the word "class" as the attribute used to denote the class-wide types like T'CLASS, to distinguish them from the "specific" types T declared by a normal type declaration. This use has a nice characteristic, since it relates to the Ada 83 (and 9X) concept of a "class of types" (a set of types with similar values and operations) and to the OOP notion of subclassing, where a subprogram whose formal parameter is of some specific "class" can in fact accept an actual of any subclass as well. "Specific" types in Ada 9X don't have this subclass property. Class-wide types like T'CLASS do. Furthermore, T'CLASS reads well as the type of a formal parameter, or the designated type of an access type, suggesting the fact that an instance of any type in the class is an acceptable actual parameter or designated object. For example: procedure Reset(D : Device'CLASS) is begin Close(D); Open(D); end Reset; or: type Widget_Ptr is access Root_Widget'CLASS; The use of the word "CLASS" in these contexts conveys the information that class-wide matching (or approximately equivalently "subclass matching") is provided here. When this marker "CLASS" is not present, one can generally assume that normal strong type checking and static binding is taking place. In other words, the term CLASS highlights exactly those places where run-time polymorphism, class-wide matching, dynamic binding, etc. are occuring, in a way that is consistent with the Ada definition of "class" as a set of types, and suggestive of the subclassing property of other object-oriented languages. USE OF THE TERM "TAGGED" When initially working on the Ada 9X proposals for OOP, we considered allowing any type to be extended, or at least any record or private type. However, as we worked out the semantic and implementation details, it became clear that it was important to "plan ahead" for type extension. If any type could be extended, then all types would have to "plan ahead" for type extenion, leading to potential distributed space or run-time overhead. Meanwhile, from the very beginning we recognized that to support run-time polymorphism, objects (or references to them) would have to carry around something at run-time describing their "underlying" type, and clearly we didn't want to burden all types, or even all records, with the expense of carrying around such a run-time type descriptor. We struggled with how to identify such types, since from the very beginning we wanted existing "Ada 83" record or private types to be easily updated to being "Ada 9X" record or private types supporting type extension and/or polymorphism. One idea was to require an explicit declaration of the class-wide type (originally called the "universal type") as being "polymorphic" or "all T" as follows: type T is record ... type T'CLASS is polymorphic; (or "type T_CLASS is all T;") procedure Put(X : T); . . . Another was to require that at least one primitive operation (aka derivable subprogram) be marked "polymorphic" or "virtual," such as: type T is record ... procedure Put(X : T) is polymorphic; . . . We also found the need to talk about the type descriptor explicitly, and started presuming an attribute 'DESCRIPTOR such as: if X'DESCRIPTOR /= T'DESCRIPTOR then ... After much head scratching and many discussions, we came to a few conclusions: 1) DESCRIPTOR was too "mechanistic" -- a more abstract and appropriate term was "tag," since it was perfectly reasonable that the run-time indicator might be an index or other identifier rather than a reference to a type descriptor. 2) All or none of the primitive operations of a type should be "polymorphic"/"virtual" (now called "dispatching"), since going half and half created nasty hybrid situations where a certain combination of operations would be reached in a dynamic binding situation which were never reached in a static binding situation, making thorough testing virtually impossible. 3) Supporting type extension independently from polymorphism was not worth the complexity. One needed to "plan ahead" for both type extension and polymorphism, and it was unlikely that a useful set of types with polymorphism wouldn't require type extension, and that a set of related type extensions wouldn't eventually benefit from polymorphism. (A set of related type extensions without polymorphism would be like a variant record without any case statements -- conceivable, but not worth making into a special case.) 4) Explicitly declaring the class-wide type was awkward, since the type itself needed to "plan ahead" for type extension and/or polymorphism, as either extension or polymorphism might affect the desired layout for objects of the type, the heap representation, whether pass-by-reference or pass-by-value was used, etc. Taking all of this together we concluded that all types that would support type extension and polymorphism would have run-time "tag"s of some sort (pointer, index, identifier, ...) so it became natural to think of these as "tagged" types, and ultimately to use that term in the declaration of the record or private type itself. Using "polymorphic" in the declaration of the record or private type was misleading, since it was T'CLASS that was the basis of polymorphism. Furthermore, "tagged record" said exactly what was implied -- instances of this type had associated "tag"s. Another alternative was "extensible", but tagged seemed more consistent, since as with other words used to describe types in Ada, it said what the type *was*, not what was done to it. For example, record types were not called "selectable" types, nor were array types called "indexable" types, nor were limited types called "unassignable" types. Where we have ended up now is a consistent syntax that supports all 8 of the interesting combinations of tagged vs. untagged, limited vs. non-limited, and record vs. private, in the most straightforward and economical fashion, as follows: type T is [tagged] [limited] (private | record ... end record) ; The reserved words "tagged" and "limited" are both permitted as prefixes to private or record type definitions, and both are inherited automatically on all type derivations. "Tagged" and "limited" are both permitted on generic formal private type declarations as well. From a readability and "speakability" point of view, both the terms "tagged" and "limited" are grammatically and semantically appropriate modifiers to private, record, object, type, subtype, etc. Other alternative terms don't work as well as modifiers (for what that's worth), since they generally imply something that is either redundant or misleading about the associated entity (e.g. a "class object" in SmallTalk is the same thing as a class -- an instance of a metaclass; an "extensible object" would imply that the object is extensible, when in fact it is only true that extensions of its type can be declared, implying that even the type itself is not really "extensible.") Of course readability and speakability are somewhat a matter of taste, but a careful choice of words can help to minimize confusion in the long run. Tagged has the advantage of conveying exactly what it says, thereby minimizing any chance of confusion. MARKETING-RELATED ISSUES Ada 9X supports object-oriented programming. For some people, Ada 9X will be "Ada 83 with OOP." "Objects" and object-oriented and OOD and OOA and OOX are the mantras of the 90s, and Ada 9X will be able to absolutely claim that it is an object-oriented programming language. Ada 9X is also a real-time and embedded systems programming language. And finally, we believe Ada 9X will continue to be the premier language in providing language support for software engineering, with the strong and consistent separation of logical interface (the visible part), physical interface (the private part), and implementation (the body), combined with the hierarchical name space and visibility control, and strong type checking. There has been significant discussion of where exactly the word "class" should appear in Ada 9X. This seems pretty much irrelevant to the pure marketing concerns, since it is "object-oriented programming," "object"-this and "object"-that that the world is talking about. However, it is useful if the term, if used at all, is not inconsistent with how it is used in other OO languages, and fully consistent with how it is used throughout the rest of Ada. For those OO languages that have a "class construct" (Simula 67, SmallTalk, C++, Eiffel), a class doubles as both a module and a type. However, other OO languages have also used the term class (as opposed to "class construct") to correspond to the kind of type that supports the "subclassing" property mentioned above. Since derived types are definitely not subclasses, it would be misleading to use any syntax that implied a "specific" derived type was a class. Similarly, to use the term "class" as a prefix of a construct like a package or a task, we would definitely be relating to the languages that have a "class construct" where type = module. This could be quite misleading, and would suggest that somehow these types are very different from records or private types. On the other hand, to use the term "class" to identify class-wide types, which do have the subclassing property, by providing class-wide matching in the Ada sense of "class of types," we nicely link up the OOPL concept of class with the Ada concept of class. Hence, we believe that Ada 9X has all the needed characteristics to succeed in the pure "marketing" domain, while also preserving the technical consistency and flexibility to succeed with the programmers coming from Ada 83 or other OOPLs. SPECIFIC SYNTAX ALTERNATIVES The above somewhat rambling discussion is intended to explain the rationale behind the current Ada 9X object-oriented features and their associated syntax. Syntax and semantics go hand in hand, because one should be suggestive of the other. However, it seems useful to list a few possible alternative syntaxes for defining a tagged type, and a type extension, and point out some of their specific disadvantages. 1) "Prefix" notation: class type T is ... For an Ada 9X programmer coming from SmallTalk or CLOS, the phrase "class type" suggests a metaclass, not a class. The term "class object" is even worse, since it is a term already used in SmallTalk (and the OOP literature) as the run-time representation of a class, rather than an instance of a class. For an Ada 9X programmer coming from C++, Eiffel, etc., the phrase "class type" is from the Department of Redundancy Department. It sounds roughly as redundant as "van truck" or "ocean liner ship." For an Ada 9X programmer familiar with the rest of the language, a type declaration introduced by a prefix (i.e. "task" or "protected") will suggest some or all of the following: a) The type is doubling as a module (i.e. program unit), and a body will be required; b) The type has something to do with concurrency; c) Operations may be declared "inside" the type; these operations have special *access* to the single instance currently being manipulated; this is in distinction to operations declared inside a package which have special *visibility* to all instances of the private types of the package; d) The word "type" is optional, allowing the declaration of single objects of an anonymous type. ------------- class T is ... This syntax represents a completely new syntax for a type definition. Furthermore, it clearly suggests that T is a "class," even though it does not have the subclass property when used as the type of a formal parameter; it will be confusing if T'CLASS is called a "class-wide type" and T is called a "class." For a programmer coming from another OOPL, this syntax suggests the notion of a "class construct" (as opposed to just classes). In all OOPLs with a "class construct," the class construct defines a type and doubles as a module, with instance variables and methods, as well as "class" (a.k.a "static" in C++) variables and methods, declared "inside." Ada 9X has chosen a very different approach, with instance methods declared in the enclosing package, and no concept of "class"/static variables and methods (as they are redundant with the normal contents of a package). --------- 2) "Class" worked into the syntax elsewhere than as a prefix type T is [limited] class ... end class; type T is [limited] private class; type T2 is new T with class ... end class; type T2 is new T with private class; This use of class is also misleading, since again it is T'CLASS, not T, that is in fact most like an OO class. Particulary in the extension syntax "new T with class ... end class" it seems to be misusing the term. Certainly the extension part is not a class itself, whereas it can be thought of as a record, which is consistent with the "with record ... end record" syntax. type T is new [limited] class with record ... end record; type T is new [limited] class with private; (type extension syntax would remain as currently proposed) This again would certainly imply that T is a class, which it isn't, and would similarly result in confusion with T'CLASS. type T is root of [limited] class with record ... end record; type T is root of [limited] class with private; (type extension syntax would remain as currently proposed) Well now we are saying the truth, but not the whole truth, since every extension of T is also the root of its own (sub)class. Furthermore, we now say "root of class with" instead of simply "tagged." What would these types be called informally? "Root" types? That is true, but hardly unique to these types. 3) Other words like "extensible" type T is extensible [limited] record ... end record; type T is extensible [limited] private; (type extension syntax would remain as currently proposed) This syntax avoids any confusion with T'CLASS, but it is not consistent with the other naming conventions for kinds of types, where they are described by what the "are" rather than what one does to them (we don't call arrays "indexable" types nor records "selectable" types). Furthermore, the term "extensible" is unfortunate as an adjective when applied to objects (or even types), since the objects are not extensible, but rather extensions of the types can be declared as distinct types. One might think of a class-wide object as extensible, but in fact, as currently proposed,they are always constrained by their initial tag and discriminants. type T is polymorphic [limited] record ... end record; type T is polymorphic [limited] private; (type extension syntax would remain as currently proposed) This has some of the same problems as "extensible." It is not T that is "polymorphic" but rather T'CLASS. Furthermore, "polymorphic" is a term with a lot of definitions, some of which are somewhat hard to pin down. There is run-time polymorphism, compile-time polymorphism (e.g. generics), ad hoc polymorphism (e.g. overloading), inclusion polymophism (e.g. based on inheritance), parametric polymorphism (e.g. based on type parameters), etc. One suggestion was "type T is oriented [limited] (record | private) ..." Then we would have "oriented objects"! Perhaps a bit too Californian... ------------ Here is a summary of our current position: We believe that "tagged" most accurately captures the nature of these types, combines well with "limited," "record," "private," "object," "type," etc., and has the least chance of creating confusion for the Ada 83 programmer and for the programmer coming to Ada from other OOPLs. The prominent use of T'CLASS in 9X ensures that we will make an intuitive and technically sound connection to the dynamic binding and subclass matching familiar from other OOPLs. Inserting the word "class" in a technically inappropriate part of the syntax has the danger of doing more harm than good, particularly since it is already in use elsewhere in 9X in an appropriate and prominent place.