!standard 03.04 (02) 04-05-27 AI95-00251/13 !standard 02.09 (03) !standard 03.02.01 (02) !standard 03.04 (03) !standard 03.04 (08) !standard 03.04 (23) !standard 03.04 (35) !standard 03.04.01 (02) !standard 03.04.01 (10) !standard 03.09.03 (04) !standard 03.09.03 (05) !standard 03.09.04 (01) !standard 04.05.02 (03) !standard 04.06 (08-10) !standard 04.06 (11/1) !standard 04.06 (12/1) !standard 04.06 (12.1/1) !standard 04.06 (13-24) !standard 07.03 (02) !standard 08.03 (12) !standard 08.03 (26/1) !standard 12.05 (03) !standard 12.05.01 (03) !standard 12.05.01 (15) !standard 12.05.05 (01) !standard 13.14 (07) !class amendment 00-12-04 !status Amendment 200Y 04-03-29 !status WG9 Approved 04-06-18 !status ARG Approved 7-0-1 04-03-07 !status work item 00-12-04 !status received 00-12-04 !priority High !difficulty Hard !subject Abstract Interfaces to provide multiple inheritance !summary This proposal adds "interface" types to the standard as a kind of componentless abstract tagged type. A tagged type may be derived from one or more such interfaces as well as possibly from one tagged type; this provides a limited form of multiple inheritance. Dispatching calls through the primitives of an interface dispatch to code bodies associated with specific tagged types that are derived from that interface. This proposal also clarifies that it is always legal to convert between two types if they have a common ancestor and they meet the restrictions of 4.6(22-23). !problem A number of recent language designs have adopted a compromise between full multiple inheritance and strict single inheritance, using a concept called "interface" types. An interface consists solely of a set of operation specifications -- the interface type has no data components and no operation implementations. In these designs a type may "implement" multiple interfaces, but can inherit code from only one parent type. This compromise has been found to have much of the power of multiple inheritance, without most of the implementation and semantic difficulties. This allows a single type to "masquerade" as a number of different types so that it may take advantage of existing abstractions that expect an object that provides a certain set of primitives. This kind of flexibility is possible in Ada currently only using generics, or relatively complicated mechanisms using access discriminants. The solutions using generics don't really work when the abstraction itself cannot be represented as a generic. For example, a graphical subsystem may have a linked list of "observer" objects, each of which must implement an operation to receive notification when the observed object changes. This is not amenable to a generic mix-in approach. You can't mix-in the ability to be on a linked list of observers. Access discriminants can accomplish this, at the expense of making the type limited, and requiring a fair amount of mechanism. These problems would be overcome by Ada adopting the interface concept in some way. Moreover, this would make interfacing with Java and the new ".net" infrastructure from Microsoft would be significantly smoother. !proposal An interface type is a kind of abstract tagged type without any components. All primitive subprograms must be abstract or null. (A null procedure is introduced by AI-348 and behaves as if it had a body consisting solely of a null statement.) So we might have package PkgI1 is type I1 is interface; procedure P (X: I1) is abstract; procedure Q (X: I1) is null; end PkgI1; We can now derive a tagged type from one or more interface types plus possibly one tagged type. In other words we can derive a tagged type from several other types (its ancestor types) but only one of these can be a normal tagged type (it has to be written first). If we have an existing normal tagged type T1 in a package PkgT1 with operations P1, P2 and so on we could now write with PkgI1, PkgT1; package PkgDT is type DT is new PkgT1.T1 and PkgI1.I1 with ...; procedure P(X: DT); -- possibly other ops of DT end PkgDT; We must of course provide a concrete procedure for P inherited from the interface I1. We could also provide an overriding for Q but if we don't then we simply inherit the null procedure of I1. We could also override the inherited operations P1 and P2 from T1 in the usual way. We can also derive a new interface from other interfaces thus type I1 is interface; ... type I2 is interface; ... type I3 is interface I1; ... type I4 is interface I1 and I2; ... When we derive interfaces in this way we can add new operations so that the new interface such as I4 will have all the operations of both I1 and I2 plus possibly some others declared specifically as operations of I4. All these operations must be abstract or null and there are fairly obvious rules regarding what happens if two or more of the ancestor interfaces have the same operation. Thus a null operation overrides an abstract one. More generally if we have tagged types T1, T2 and so on we can derive further tagged types thus type DT1 is new T1 and I1 and I2 with null record; -- no additional components ... type DT2 is new I3 and I4 with record ... end record; -- has additional components ... The first in the list of ancestor types is always known as the parent type. The parent type might or might not be an interface. All other ancestor types must be interfaces. Thus the following is not permitted type DT3 is new T1 and T2 with .... -- illegal Class-wide types also apply to interface types. The class-wide type I1'Class covers all the types derived from the interface I1 (both other interfaces as well as normal tagged types). We can then dispatch using an object of a concrete tagged type in that class in the usual way since we know that any abstract operation of I1 will have been overridden. So we might have type I1_Ref is access all T1'Class; DT1_Var : aliased DT1; Ref: I1_Ref := DT1_Var'Access; Observe that conversion is permitted between the class-wide type I1_Ref and any type which is derived from the interface type I1. We informally speak of a specific tagged type as implementing an interface from which it is derived (directly or indirectly). The phrase "implementing an interface" is not used in the wording but it is useful for purposes of discussion. Interfaces can also be used in private extensions and as generic parameters. Thus type PT1 is new T1 and I2 and I3 with private; ... private type PT1 is new DT1 and I2 and I3 with null record; An important rule regarding private extensions is that the full view and the partial view must agree with respect to the set of interfaces they implement. Thus although the parent in the full view need not be T1 but can be any type derived from T1, the same is not true of the interfaces which must be such that they both implement the same set exactly. Generic parameters take the form generic type FI is interface I1 and I2; package... and then the actual parameter must be an interface which implements all the ancestors I1, I2 etc. The formal could also just be "type FI is interface;" in which case the actual parameter can be any interface. (There might be subprograms passed as further parameters which would require that the actual has certain operations.) The interfaces I1 and I2 might themselves be formal parameters occuring earlier in the parameter list. Interfaces can also be limited. !wording In 2.9(3) as modified by AI-284 add the following to the list of nonreserved keywords interface 3.2.1 Type Declarations Add to syntax type_definition ::= ... | interface_type_definition 3.4 Derived Types and Classes Replace syntax section with interface_list ::= interface_subtype_mark {AND interface_subtype_mark} derived_type_definition ::= [ABSTRACT] NEW parent_subtype_indication [[AND interface_list] record_extension_part] Add at the end of paragraph 3: A derived type has one parent type and zero or more interface ancestor types. Replace paragraph 8 with: Each class of types that includes the parent type or an interface ancestor type also includes the derived type. Add after paragraph 23: If a type declaration names an interface type in an interface_list, then the declared type inherits any user-defined primitive subprograms of the interface type in the same way. Add after paragraph 35 (in the Notes section): 18 An interface type which has an interface ancestor "is derived from" that type, and therefore is a derived type. A derived_type_definition, however, never defines an interface type. 3.4.1 Derivation Classes Insert after the first sentence of paragraph 2: A derived type or interface type is also derived from each of its interface ancestor types, if any. Replace the last sentence of paragraph 10 with: An *ultimate ancestor* of a type is an ancestor of that type that is not a descendant of any other type. Each untagged type has a unique ultimate ancestor. 3.9.3 Abstract Types and Subprograms Replace paragraphs 4-5 with If a type inherits a subprogram corresponding to an abstract subprogram or to a function with a controlling result, then - If the inheriting type is abstract or untagged, the inherited subprogram is abstract. 3.9.4 Interface Types (This section is entirely new.) An interface type is an abstract tagged type which provides a restricted form of multiple inheritance. A tagged type may be derived from multiple interface types. Syntax interface_type_definition ::= [LIMITED] INTERFACE [interface_list] Static Semantics An interface type (also called an "interface") is a specific abstract tagged type that is defined by an interface_type_definition. [An interface type has no components.] AARM Note: This follows from the syntax. Legality Rules All user-defined primitive subprograms of an interface type shall be abstract subprograms or null procedures. The type of a subtype named in an interface_list shall be an interface type. If a type declaration names an interface type in an interface_list, then the accessibility level of the declared type shall not be statically deeper than that of the interface type; also, the declared type shall not be declared in a generic body if the interface type is declared outside that body. A descendant of an interface type shall be limited if and only if the interface type is limited. AARM Note: Reason: Without this rule, an implementation would no longer know statically whether a dispatching function call is a call to a function with a return-by-reference result. This could be implemented, but it is a substantial and unnecessary complication. The complications of AI-318 occur precisely because we're trying to avoid runtime choices between normal and return-by-reference functions. Besides, the language has, as a matter of general principle, steered away from constructs which would allow a limited view of an object of a non-limited tagged type. We may want to reconsider this if some form of AI-318 is included in the standard. End AARM Note. A full view shall be a descendant of an interface type if and only if the corresponding partial view (if any) is also a descendant of the interface type. AARM Note: Reason: Consider the following example: package P is package Pkg is type Ifc is interface; procedure Foo (X : Ifc) is abstract; end; type Parent_1 is tagged null record; type T1 is new Parent_1 with private; private type Parent_2 is new Parent_1 and Pkg.Ifc with null record; procedure Foo (X : Parent_2); -- Foo #1 type T1 is new Parent_2 with null record; end; with P; package P_Client is type T2 is new P.T1 and P.Pkg.Ifc with null record; procedure Foo (X : T2); -- Foo #2 X : T2; end P2_Client; with P_Client; package body P is ... begin Pkg.Foo (Pkg.Ifc'Class (P_Client.X)); -- call Foo #2 Pkg.Foo (Pkg.Ifc'Class (T1 (P_Client.X))); -- call Foo #1 end P2; If this example were legal (it is illegal because the completion of T1 is descended from an interface that the partial view is not descended from), then we would have two dispatching calls to Pkg.Foo with the two controlling operands having the same tag and yet different bodies would be executed. The two conversions to Pkg.Ifc'Class would map Pkg.Foo to different slots in the same dispatch table because the source types of the conversions are different. This would be bad. End AARM Note For an interface type declared in a visible part, a primitive subprogram shall not be declared in the private part. In addition to the places where Legality Rules normally apply (see 12.3), these rules apply also in the private part of an instance of a generic unit. 4.5.2 Relational Operators and Membership Tests Replace paragraph 3 with The tested type of a membership test is the type of the range or the type determined by the subtype_mark. If the tested type is tagged, then the simple_expression shall resolve to be of a type that is convertible (see 4.6) to the tested type; if untagged, the expected type for the simple_expression is the tested type. 4.6 Type Conversions Insert the following before paragraph 8: In a view conversion for an untagged type, the target type shall be convertible (back) to the operand type. If there is a type that is an ancestor of both the target type and the operand type, then - The target type shall be untagged; or - The operand type shall be covered by or descended from the target type; or - The operand type shall be a class-wide type that covers the target type; or - The operand and target types shall both be class-wide types and the specific type associated with at least one of them shall be an interface type. If there is no type that is an ancestor of both the target type and the operand type, then Existing paragraphs 8, 9-12.1/1, 13-17, and 18-20 become bullets under "If there is no type ...". Delete the paragraphs 21-24 (they're included above). 7.3 Private Types and Private Extensions Replace private_extension_declaration ::= TYPE defining_identifier [discriminant_part] IS [ABSTRACT] NEW ancestor_subtype_indication WITH PRIVATE; with private_extension_declaration ::= TYPE defining_identifier [discriminant_part] IS [ABSTRACT] NEW ancestor_subtype_indication [AND interface_list] WITH PRIVATE; 8.3 Visibility Add a new bullet into paragraphs 9/1 - 13 (the entire group is given here to make it easier to evaluate the changes): Two homographs are not generally allowed immediately within the same declarative region unless one overrides the other (see Legality Rules below). The only declarations that are overridable are the implicit declarations for predefined operators and inherited primitive subprograms. A declaration overrides another homograph that occurs immediately within the same declarative region in the following cases: * A declaration that is not overridable overrides one that is overridable, regardless of which declaration occurs first; * The implicit declaration of an inherited operator overrides that of a predefined operator; * An implicit declaration of an inherited subprogram overrides a previous implicit declaration of an inherited subprogram. * If two or more homographs are implicitly declared at the same place: - If one is a non-null non-abstract subprogram, then it overrides all which are null or abstract subprograms. - If all are null procedures or abstract subprograms, then any null procedure overrides all abstract subprograms; if more than one homograph remains that is not thus overridden, then one is chosen arbitrarily to override the others. AARM Note: Discussion: It is intended that in the case where the implementation arbitrarily chooses one overrider from among a group of inherited subprograms, users should be unable to determine which member was chosen. This rule is needed in order to allow package Outer is package P1 is type Ifc1 is interface; procedure Null_Procedure (X : Ifc1) is null; procedure Abstract_Subp (X : Ifc1) is abstract; end P1; package P2 is type Ifc2 is interface; procedure Null_Procedure (X : Ifc2) is null; procedure Abstract_Subp (X : Ifc2) is abstract; end P2; type T is new P1.Ifc1 and P2.Ifc2 with null record; end Outer; without requiring that T explicitly override any of its inherited operations. End AARM Note * For an implicit declaration of a primitive subprogram in a generic unit, there is a copy of this declaration in an instance. However, a whole new set of primitive subprograms is implicitly declared for each type declared within the visible part of the instance. These new declarations occur immediately after the type declaration, and override the copied ones. The copied ones can be called only from within the instance; the new ones can be called only from outside the instance, although for tagged types, the body of a new one can be executed by a call to an old one. Replace 26/1 with: A non-overridable declaration is illegal if there is a homograph occurring immediately within the same declarative region that is visible at the place of the declaration, and is not hidden from all visibility by the non-overridable declaration. In addition, a type extension is illegal if somewhere within its immediate scope it has two visible components with the same name. Similarly, the context_clause for a subunit is illegal if it mentions (in a with_clause) some library unit, and there is a homograph of the library unit that is visible at the place of the corresponding stub, and the homograph and the mentioned library unit are both declared immediately within the same declarative region. If two or more homographs are implicitly declared at the same place (and not overridden by a non-overridable declaration) then at most one shall be a non-null non-abstract subprogram. If all are null or abstract, then all of the null subprograms shall be fully conformant with one another. If all are abstract, then all of the subprograms shall be fully conformant with one another. All of these rules also apply to dispatching operations declared in the visible part of an instance of a generic unit. However, they do not apply to other overloadable declarations in an instance; such declarations may have type conformant profiles in the instance, so long as the corresponding declarations in the generic were not type conformant. 12.5 Formal Types Add to syntax formal_type_definition ::= ... | formal_interface_type_definition 12.5.1 Formal Private and Derived Types Replace formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [WITH PRIVATE] with formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [[AND interface_list] WITH PRIVATE] Add after paragraph 15: The actual type shall be a descendant of every ancestor of the formal type. 12.5.5 Formal Interface Types (This section is entirely new.) The class determined for a formal interface type is the class of all interface types. Syntax formal_interface_type_definition ::= interface_type_definition Legality Rules The actual type shall be an interface type. AARM Note: Reason: The class of all interface types includes non-interface descendants of interface types. Such types must not match a formal interface. End AARM Note. The actual type shall be a descendant of every ancestor of the formal type. The actual type shall be limited if and only if the formal type is limited. 13.14 Freezing Rules Add a further bullet after paragraph 7: The declaration of a specific descendant of an interface type freezes the interface type. !discussion All primitives of an interface are required to be abstract or null to minimize difficulty in resolving what happens when the "same" primitive is inherited from two (or more) ancestor types. If only one ancestor type is permitted to have non-abstract, non-null primitives, no question arises as to which implementation to use. For simplicity the first type in the list of ancestor types is called the parent type and this parent type is the only ancestor that can be a normal tagged type - note that the parent type can itself be an interface type. Languages that support full multiple inheritance need elaborate rules for resolving these kinds of conflicts and lead to confusion in the mind of user. Moreover, no data components are allowed in interfaces to avoid implementation complexity and inefficiency. If data components could be inherited from multiple ancestors, then the offset of a component of an interface could not be determined statically. It would make component access as complicated and expensive as calling a primitive (which is discussed below), or require recompilation of inherited primitives even when not overridden. Inheritance then becomes more like macro expansion. We considered avoiding the introduction of the new concept of an interface type by simply permitting inheritance from several tagged types provided only one had concrete operations and components. However, this approach would prevent the clean introduction of the interface concept into generics, and would be fragile: if a maintenance programmer added a component or concrete operation, other, possibly distant, code would become illegal. Moreover, this is probably less easy for the user to understand - particularly if the user is familiar with Java. Having decided that a new concept was required, many forms of syntax were considered. The new keyword interface was chosen as best reflecting the nature of the concept. However, it was recognized that Interface is probably a popular identifier and that to make it a reserved word would cause irritation among the user community. Thus the concept of keywords was introduced (see Ai-284). Keywords cover all the existing reserved words and these remain reserved but unreserved keywords are now also permitted. Confusion between their use as keywords and use as identifiers is avoided by ensuring that unreserved keywords are only used in syntactically clear situations. Observe that the keyword interface is always followed by a list of interface identifiers but that this list may be null. Thus "interface" may be followed just by a semicolon or by the identifier of an interface. The rules are structured to ensure that an existing componentless abstract tagged type can be changed to being an interface type with minimal disruption to the existing code. Because an interface type is an abstract tagged type, it can be used anywhere an abstract tagged private (or null record) type is permitted. We disallow "private" inheriting from interfaces, because without allocating a complete separate set of dispatching operation "slots" for the corresponding operations, there is no good way to prevent these operations from being unintentionally overridden outside of the package by a descendant that explicitly implements the same interface. For example if I1 is an interface type with a primitive P and T1 is some tagged type as in the proposal section then we might otherwise write package Pkg1 is type PT1 is tagged private; ... private type PT1 is new T1 and I1 with ... -- illegal private inheritance of interface procedure P(X: PT1); -- overrides primitive P of interface I1 end Pkg1; ... type PT2 is new Pkg1.PT1 and I1 with ... procedure P(X: PT2); -- if primitive operations such as P of I1 are overridden for PT2, -- they will override operations associated with PT1's private -- inheritance of I1, violating the privateness of the inheritance. In the above case, the partial view of PT1 is allowed to be type PT1 is new I1 with private; This makes it visible that the interface I1 is inherited and thus that its primitives might well be overriden outside Pkg1. Possible Implementation Model As an implementation model, one can think of a type which inherits operations from an interface type as defining a mapping from the primitive operations of the interface type (perhaps represented as dispatch table slot numbers) to those of the inheriting type. A dispatching call to an operation of an interface type is then accomplished by mapping the called operation to the corresponding operation of the controlling operand's type. Alternatively, one might construct a separate dispatch table representing this alternative "view" of the "real" dispatch table, but that would require more work when deriving from a type which implements an interface. The slot-to-slot mapping remains valid and can be reused for the derived type, whereas a new alternative "view" of the dispatch table of the derived type would need to be constructed. A value of type Some_Interface_Type'Class can then be represented at runtime as a record address paired (perhaps only conceptually - see next paragraph) with a reference to this dispatch table permutation map. Consider the example of a call to Some_Interface_Type'Class'Input occurring as the controlling operand of a dispatching call. The implementation must be able to determine where to dispatch to in this case. In terms of the "slot-to-slot mapping" implementation model, this means that an implementation needs to provide a mapping at runtime which takes the tag of a specific tagged type and of an interface type (which is implemented by the specific type) and returns a reference to the associated dispatch table permutation map (the "slot-to-slot" map). Given this mapping, a "thin pointer" representation becomes feasible. In other words, a value of type Some_Interface_Type'Class could be represented as an unaugmented record address. Alternatively, a "fat pointer" implementation model is possible. An interface "view" of an object is represented as a pair of pointers, one to an interface-specific dispatch table (or permutation map), and the other to the tagged object of the implementing type. The interface-specific dispatch table would have a layout determined by the order in which the primitives of the interface were declared, while its content would be determined by the particular type implementing the interface. Intermediate approaches between these two extremes are also possible (e.g. use the thin pointer representation, but associate an implicit parameter with formal parameters). Note also that the thin/fat implementation decision is orthogonal to the decision about whether to store the details of how a type implements an interface (hereafter referred to as an "interface implementation descriptor") as a new dispatch table, as a slot-to-slot map, or in some other form. In any case, a dispatch table could contain a map of some kind (e.g. an array) mapping interface type tag values to these "interface implementation descriptors", with one entry in the map for each interface implemented by the type. This would meet the aforementioned need for a mapping from the tag of a specific type and the tag of an interface type which it implements to the corresponding interface implementation descriptor. The Some_Interface_Type'Class'Input example described above also illustrates some of the difficulties that an implementation might run into if two types which both implement a given interface store their tag components at different locations. In order to perform the dispatching call, the tag of the controlling operand must be located. It is expected that most implementations will solve this problem by requiring that every tag component must reside at the same offset (typically zero) for all tagged types. ---- Technical notes: 1) Inherited homographs must be overridden. If the homographs are not mode-conformant, then this may be impossible. Life's hard. 2) Nothing here (in particular, nothing in the changes to the visibility rules in 8.3) was *intended* to change the semantics of a program which declares no interface types. Consider, however: generic type T1 is private; type T2 is private; package G is type T is null record; procedure P (X : T; Y : T1); procedure P (X : T; Y : T2); end G; package I is new G (Integer, Integer); -- exports homographs type D is new I.T; -- formerly legal, now illegal. Disallowing this case does not seem like a bad thing, but it is a (very minor) change. 3) A type derived from an interface type should inherit the same subprograms as a type which is derived from both the interface type and an ancestor of the interface type. The two type definitions should be indistinguishable, in the sense that adding or deleting the explicit redundant derivation relationship should be a semantics-preserving transformation. 4) It is intended that there should be no real difference between the two forms of derivation from interface types. For example, replacing the declaration type T is new Interface_Type_1 and Interface_Type_2 with null record; with type T is new Interface_Type_2 and Interface_Type_1 with null record; should be semantics-preserving. 5) Note that an interface type declared in a visible part is not allowed to have primitive operations declared in the private part. A dispatching call to a primitive of an interface type will execute the body of a corresponding routine associated with the specific type of the controlling operand. Without this restriction, it is possible that the specific type might provide no such routine. It would be OK to follow the example of the rules in 3.9.3(10) and allow this in the case where the subprogram declared in the private part "is overriding an abstract subprogram implicitly declared in the visible part", but this doesn't seem to be worth the bother because this could only be used to override an abstract procedure with a null procedure. The changes to section 4.6 also include a solution for the problem originally identified in AI-219 (which is now folded into this AI). Originally, 4.6(21) included the following: "If the target type is not included in any of the above 4 cases, then ..." Thus, 4.6(21-23) only applied if the target type was not a numeric type, an array type, a general access type, or an access-to-subprogram type. This turned out to be a mistake. It might seem at first that this restriction would have no consequences - if the two types are known to have a common ancestor, then surely the characteristics mentioned in 4.6(8-20) would all match up and the conversion would be legal. This is not always true. Consider the following example: package P is type Target is private; private type Target is range 1 .. 10; ... ; end P; with P; package Q is type Source is new P.Target; function Source_Value return Source; end Q; with Q package body P is Converted : Target := Target (Q.Source_Value); -- legal ... end P; As seen from the point of the type conversion, Target is a numeric type although Source is not. With the original wording, 4.6(21-23) does not apply and the conversion would be illegal. Thus, the Standard as written is incompatible with Ada 83, which allowed conversions between any two types related by derivation. Certainly no such incompatibility was intended; no such incompatibility is listed under the "Incompatibilities with Ada 83" heading in the AARM. This AI's reorganization of 4.6 addresses this problem. !example An example involving interface types: type T is tagged null record; T_Var : T; package P1 is type Ifc1 is interface; procedure Op (X : Ifc1) is abstract; procedure Op1_A (X : Ifc1) is abstract; procedure Op1_B (X : Ifc1) is abstract; type Ref is access all Ifc1'Class; Procedure Foo (X : Ref); end; package P2 is type Ifc2 is interface; procedure Op (X : Ifc2) is abstract; procedure Op2_A (X : Ifc2) is abstract; procedure Op2_B (X : Ifc2) is abstract; type Ref is access all Ifc2'Class; Procedure Foo (X : Ref); end; package body P1 is ... procedure Foo (X : Ref) is type Stream_Ptr is access all Ada.Streams.Root_Stream_Type'Class; The_Stream : constant Stream_Ptr := ...; begin Op1_A (X.all); Op1_B (X.all); Ifc1'Class'Output (The_Stream, X.all); if X.all in T'Class then T_Var := T (X.all); end if; end Foo; end P1; package body P2 is ... end P2; type D is new P1.Ifc1 and P2.Ifc2 with record F1, F2 : Integer; end record; procedure Op1_A (X : D); procedure Op1_B (X : D); procedure Op (X : D); procedure Op2_A (X : D); procedure Op2_B (X : D); ... X : aliased D; begin P1.Foo (X'Access); P2.Foo (X'Access); P1.Op (P1.Ifc1'Class (X)); P2.Op (P2.Ifc2'Class (X)); end; -------- A somewhat less artificial example: package Object_Monitoring is type Monitored_Object is interface; procedure Display (Object : Monitored_Object) is abstract; type Object_Reference is access all Monitored_Object'Class; for Object_Reference'Storage_Size use 0; procedure Register (Object : Object_Reference); procedure Unregister (Object : Object_Reference); procedure Display_Registered_Objects; end; package body Object_Monitoring is package Object_Reference_Sets is new ... ; Registered_Objects : Object_Reference_Sets.Set; procedure Display_One (Object : Object_Reference) is begin Display (Object.all); end; procedure Display_Registered_Objects is begin Object_Reference_Sets.Visit_All (The_Set => Registered_Objects, Visit_One => Display_One'Access); end; ... end Object_Monitoring; package Pkg1 is ... end Pkg1; with Object_Monitoring; package body Pkg1 is type T is tagged record F1, F2 : Integer; end record; type Monitored_T is new T and Object_Monitoring.Monitored_Object with ...; procedure Display (Object : Monitored_T) is begin ... end; X : aliased Monitored_T; ... begin Object_Monitoring.Register (X'access); end; !corrigendum 2.9(3) @drepl NOTES@hr 6 The reserved words appear in @b in this International Standard, except when used in the @fa of an attribute (see 4.1.4). Lower case boldface is also used for a reserved word in a @fa used as an @fa. This is merely a convention @emdash programs may be written in whatever typeface is desired and available. @dby @b is a nonreserved keyword. NOTES@hr 6 The reserved words appear in @b in this International Standard, except when used in the @fa of an attribute (see 4.1.4). Lower case boldface is also used for a reserved word in a @fa used as an @fa. This is merely a convention @emdash programs may be written in whatever typeface is desired and available. !corrigendum 3.2.1(4) @drepl @xcode<@fa> @dby @xcode<@fa> !corrigendum 3.4(2) @drepl @xcode<@fa@ft<@b>@fa<] >@ft<@b @i>@fa> @dby @xcode<@fa@ft<@b>@fa< interface_subtype_mark}>> @xcode<@fa@ft<@b>@fa<] >@ft<@b @i>@fa@ft<@b>@fa< interface_list] record_extension_part]>> !corrigendum 3.4(3) @drepl The @i@fa defines the parent subtype; its type is the parent type. @dby The @i@fa defines the parent subtype; its type is the parent type. A derived type has one parent type and zero or more interface ancestor types. !corrigendum 3.4(8) @drepl @xbullet @dby @xbullet !corrigendum 3.4(23) @dinsa If a primitive subprogram of the parent type is visible at the place of the @fa, then the corresponding inherited subprogram is implicitly declared immediately after the @fa. Otherwise, the inherited subprogram is implicitly declared later or not at all, as explained in 7.3.1. @dinst If a type declaration names an interface type in an @fa, then the declared type inherits any user-defined primitive subprograms of the interface type in the same way. !corrigendum 3.4(35) @dinsa @xindent<@s9<17 If the reserved word @b is given in the declaration of a type, the type is abstract (see 3.9.3).>> @dinst @xindent<@s9<18 An interface type which has an interface ancestor "is derived from" that type, and therefore is a derived type. A @fa, however, never defines an interface type.>> !corrigendum 3.4.1(02) @drepl A derived type is @i its parent type @i; it is derived @i from any type from which its parent type is derived. The derivation class of types for a type @i (also called the class @i at @i) is the set consisting of @i (the @i of the class) and all types derived from @i (directly or indirectly) plus any associated universal or class-wide types (defined below). @dby A derived type is @i its parent type @i; it is derived @i from any type from which its parent type is derived. A derived type or interface type is also derived from each of its interface ancestor types, if any. The derivation class of types for a type @i (also called the class @i at @i) is the set consisting of @i (the @i of the class) and all types derived from @i (directly or indirectly) plus any associated universal or class-wide types (defined below). !corrigendum 3.4.1(10) @drepl A specific type @i is defined to be a @i of a type @i if @i is the same as @i, or if @i is derived (directly or indirectly) from @i. A class-wide type @i'Class is defined to be a descendant of type @i if @i is a descendant of @i. Similarly, the universal types are defined to be descendants of the root types of their classes. If a type @i is a descendant of a type @i, then @i is called an @i of @i. The @i of a type is the ancestor of the type that is not a descendant of any other type. @dby A specific type @i is defined to be a @i of a type @i if @i is the same as @i, or if @i is derived (directly or indirectly) from @i. A class-wide type @i'Class is defined to be a descendant of type @i if @i is a descendant of @i. Similarly, the universal types are defined to be descendants of the root types of their classes. If a type @i is a descendant of a type @i, then @i is called an @i of @i. An @i of a type is an ancestor of that type that is not a descendant of any other type. Each untagged type has a unique ultimate ancestor. !corrigendum 3.9.3(04) @drepl For a derived type, if the parent or ancestor type has an abstract primitive subprogram, or a primitive function with a controlling result, then: @dby If a type inherits a subprogram corresponding to an abstract subprogram or to a function with a controlling result, then !corrigendum 3.9.3(05) @drepl @xbullet @dby @xbullet !corrigendum 3.9.4(1) @dinsc An interface type is an abstract tagged type which provides a restricted form of multiple inheritance. A tagged type may be derived from multiple interface types. @i<@s8> @xcode<@fa@ft<@b>@fa<] >@ft<@b>@fa< [interface_list]>> @i<@s8> An interface type (also called an "interface") is a specific abstract tagged type that is defined by an @fa. An interface type has no components. @i<@s8> All user-defined primitive subprograms of an interface type shall be abstract subprograms or null procedures. The type of a subtype named in an @fa shall be an interface type. If a type declaration names an interface type in an @fa, then the accessibility level of the declared type shall not be statically deeper than that of the interface type; also, the declared type shall not be declared in a generic body if the interface type is declared outside that body. A descendant of an interface type shall be limited if and only if the interface type is limited. A full view shall be a descendant of an interface type if and only if the corresponding partial view (if any) is also a descendant of the interface type. For an interface type declared in a visible part, a primitive subprogram shall not be declared in the private part. In addition to the places where Legality Rules normally apply (see 12.3), these rules apply also in the private part of an instance of a generic unit. !corrigendum 4.5.2(3) @drepl The @i of a membership test is the type of the @fa or the type determined by the @fa. If the tested type is tagged, then the @fa shall resolve to be of a type that covers or is covered by the tested type; if untagged, the expected type for the @fa is the tested type. @dby The @i of a membership test is the type of the @fa or the type determined by the @fa. If the tested type is tagged, then then the @fa shall resolve to be of a type that is convertible (see 4.6) to the tested type; if untagged, the expected type for the @fa is the tested type. !corrigendum 4.6(8) @drepl If the target type is a numeric type, then the operand type shall be a numeric type. @dby In a view conversion for an untagged type, the target type shall be convertible (back) to the operand type. If there is a type that is an ancestor of both the target type and the operand type, then @xbullet @xbullet @xbullet @xbullet If there is no type that is an ancestor of both the target type and the operand type, then @xbullet !corrigendum 4.6(9) @drepl If the target type is an array type, then the operand type shall be an array type. Further: @dby @xbullet !corrigendum 4.6(10) @drepl @xbullet @dby @xinbull !corrigendum 4.6(11/1) @drepl @xbullet @dby @xinbull !corrigendum 4.6(12/1) @drepl @xbullet @dby @xinbull !corrigendum 4.6(12.1/1) @drepl @xbullet @dby @xinbull !corrigendum 4.6(13) @drepl If the target type is a general access type, then the operand type shall be an access-to-object type. Further: @dby @xbullet !corrigendum 4.6(14) @drepl @xbullet @dby @xinbull !corrigendum 4.6(15) @drepl @xbullet @dby @xinbull !corrigendum 4.6(16) @drepl @xbullet @dby @xinbull !corrigendum 4.6(17) @drepl @xbullet @dby @xinbull !corrigendum 4.6(18) @drepl If the target type is an access-to-subprogram type, then the operand type shall be an access-to-subprogram type. Further: @dby @xbullet !corrigendum 4.6(19) @drepl @xbullet @dby @xinbull !corrigendum 4.6(20) @drepl @xbullet @dby @xinbull !corrigendum 4.6(21) @ddel If the target type is not included in any of the above four cases, there shall be a type that is an ancestor of both the target type and the operand type. Further, if the target type is tagged, then either: !corrigendum 4.6(22) @ddel @xbullet !corrigendum 4.6(23) @ddel @xbullet !corrigendum 4.6(24) @ddel In a view conversion for an untagged type, the target type shall be convertible (back) to the operand type. !corrigendum 7.3(02) @drepl @xcode<@fa@ft<@b>@fa< defining_identifier [discriminant_part] >@ft<@b>@fa< [>@ft<@b>@fa<] >@ft<@b>@fa< ancestor_subtype_indication >@ft<@b>@fa<;>> @dby @xcode<@fa@ft<@b>@fa< defining_identifier [discriminant_part] >@ft<@b>@fa< [>@ft<@b>@fa<] >@ft<@b>@fa< ancestor_subtype_indication [>@ft<@b>@fa< interface_list] >@ft<@b>@fa<;>> !corrigendum 8.3(12) @dinsa @xbullet @dinss @xbullet @xinbull @xinbull !corrigendum 8.3(26/1) @drepl A non-overridable declaration is illegal if there is a homograph occurring immediately within the same declarative region that is visible at the place of the declaration, and is not hidden from all visibility by the non-overridable declaration. In addition, a type extension is illegal if somewhere within its immediate scope it has two visible components with the same name. Similarly, the @fa for a @fa is illegal if it mentions (in a @fa) some library unit, and there is a homograph of the library unit that is visible at the place of the corresponding stub, and the homograph and the mentioned library unit are both declared immediately within the same declarative region. These rules also apply to dispatching operations declared in the visible part of an instance of a generic unit. However, they do not apply to other overloadable declarations in an instance; such declarations may have type conformant profiles in the instance, so long as the corresponding declarations in the generic were not type conformant. @dby A non-overridable declaration is illegal if there is a homograph occurring immediately within the same declarative region that is visible at the place of the declaration, and is not hidden from all visibility by the non-overridable declaration. In addition, a type extension is illegal if somewhere within its immediate scope it has two visible components with the same name. Similarly, the @fa for a @fa is illegal if it mentions (in a @fa) some library unit, and there is a homograph of the library unit that is visible at the place of the corresponding stub, and the homograph and the mentioned library unit are both declared immediately within the same declarative region. If two or more homographs are implicitly declared at the same place (and not overridden by a non-overridable declaration) then at most one shall be a non-null non-abstract subprogram. If all are null or abstract, then all of the null subprograms shall be fully conformant with one another. If all are abstract, then all of the subprograms shall be fully conformant with one another. All of these rules also apply to dispatching operations declared in the visible part of an instance of a generic unit. However, they do not apply to other overloadable declarations in an instance; such declarations may have type conformant profiles in the instance, so long as the corresponding declarations in the generic were not type conformant. !corrigendum 12.5(03) @drepl @xcode<@fa> @dby @xcode<@fa> !corrigendum 12.5.1(03) @drepl @xcode<@fa@ft<@b>@fa<] >@ft<@b>@fa< subtype_mark [>@ft<@b>@fa<]>> @dby @xcode<@fa@ft<@b>@fa<] >@ft<@b>@fa< subtype_mark [[>@ft<@b>@fa< interface_list] >@ft<@b>@fa<]>> !corrigendum 12.5.1(15) @dinsa For a generic formal type with an @fa, the actual may, but need not, have discriminants, and may be definite or indefinite. @dinst The actual type shall be a descendant of every ancestor of the formal type. !corrigendum 12.5.5(01) The class determined for a formal interface type is the class of all interface types. @i<@s8> @xcode<@fa> @i<@s8> The actual type shall be an interface type. The actual type shall be a descendant of every ancestor of the formal type. The actual type shall be limited if and only if the formal type is limited. !corrigendum 13.14(7) @dinsa @xbullet @dinst @xbullet !ACATS test (Many) ACATS Tests should be constructed to test this feature. !appendix !topic Type conversions of derived types !reference RM-4.6(8-23), 7.3.1 !date 1999-05-14 !from Bob Duff !keywords type conversion, derived type !discussion Question: Is it always legal to convert between two untagged types if they have a common ancestor? Is it always legal to convert between two tagged types if they have a common ancestor, and obey the additional restrictions of 4.6(22-23)? The answers should be "Yes", but a literal reading of the RM implies "No" in some cases. Example: package Type_Conversion is type Root is private; private type Root is access all Integer; end Type_Conversion; package Type_Conversion.Son is type Son_Type is new Root; end Type_Conversion.Son; with Type_Conversion.Son; use Type_Conversion.Son; package Type_Conversion.Daughter is type Daughter_Type is new Root; Son_Var: Son_Type; Daughter_Var: Daughter_Type; A: Daughter_Type := Daughter_Type(Son_Var); -- Legal. B: Son_Type := Son_Type(Daughter_Var); private -- Here, Daughter_Type becomes a general access type. C: Daughter_Type := Daughter_Type(Son_Var); -- Illegal?! D: Son_Type := Son_Type(Daughter_Var); end Type_Conversion.Daughter; In the declaration of C, the target type is a general access type (by 7.3.1) so 4.6(13) applies. The source type is not an access-to-object type (when viewed from that place), so it's illegal. 4.6(21) does not apply, because the target type *is* included in one of the "above four cases". That's clearly not the intent of the language designers. Reasons: First of all, I think it would be incompatible with Ada 83 (although I'm not sure -- I don't have my Ada 83 RM at hand, and I've largely forgotten that language ;-)). Certainly no such incompatibility was intended; no such incompatibility is listed under the "Incompatibilities with Ada 83" heading in the AARM. Second, consider the declaration of A. It is clearly legal. Why should moving it into the private part make it illegal? Normally moving into a private part or body *increases* the things you're allowed to do. Third, if we remove "all" from the full declaration of Root, then the declaration of C is clearly legal, because then 4.6(21) applies; it would be very strange for the "all" to have this effect. Fourth, the intent is that 4.6(21-23) should always be applicable, and that's the only sensible rule; this problem is merely an accident of wording. Note that the same wording problem arises for the special cases mentioned in 4.6(8,9,18) -- numerics, arrays, and, access-to-subprogram. I hesitate to suggest wording changes, but here's a timid attempt: Move 4.6(21-23) earlier, before all the special cases, and make it clear that the special cases only apply when the "common ancestor" case doesn't. Current behavior of some compilers: As of yesterday, the Averstar front end finds it illegal, but I've changed it to make it legal, in anticipation of the expected ARG ruling. The Rational compiler finds it legal. The GNAT compiler (version 3.11p) doesn't complain about the declaration of C, but complains on the declaration of D that Son_Type is not visible. If I remove D, GNAT is happy, so that's apparently a bug not directly related to this issue. Note: I tripped over this problem in real code I was modifying (in our run-time system); I changed an "access" to "access all", and some type conversions suddenly became illegal (according to our compiler). The comments in the compiler sent me to 4.6(13), and I was surprised to find that it was doing exactly what the RM literally says. - Bob **************************************************************** !from Tucker Taft !date Saturday, November 25, 2000 1:51 PM Gary Dismukes wrote: > > > Here is a first cut on the Multiple (interface) Inheritance AI. > > Comments welcome! > > -Tuck > (0-line AI included :-) > > Tuck, looks okay for a first cut but could use a little more detail ;-) > > -- Gary Oops. Here it is for real... (now that I have built up the suspense ;-) -Tuck ---------- !standard 03.04 (02) 00-11-21 AI95-xxx/01 !standard 03.09.01 (02) !class amendment 00-11-21 !priority High !difficulty Hard !subject Tagged Types, Abstract Interface, Multiple Inheritance !summary This amendment AI proposes that "abstract interface" types may be defined, and that a tagged type may "implement" one or more such abstract interfaces. The class-wide type associated with the abstract interface "covers" all types that implement it. Dispatching calls through the primitives of the abstract interface type dispatch to code bodies associated with specific tagged types that implement the interface. !question !recommendation Here are the proposed syntactic changes to support abstract interfaces: type_definition ::= ... | abstract_interface_definition abstract_interface_definition ::= ABSTRACT [LIMITED] derived_type_definition ::= [ABSTRACT] NEW parent_subtype_indication [AND abstract_interface_list] [record_extension_part] abstract_interface_list ::= absract_interface_subtype_mark {AND abstract_interface_subtype_mark} private_extension_declaration ::= ... [ABSTRACT] NEW ancestor_subtype_indication [AND abstract_interface_list] WITH PRIVATE; formal_type_definition ::= ... | formal_abstract_interface_definition formal_abstract_interface_definition ::= abstract_interface_definition formal_derived_type_definition ::= [ABSTRACT] NEW ancestor_subtype_indication [AND abstract_interface_list] [WITH PRIVATE]; An abstract interface type (or "abstract interface" for short) is defined by an abstract_interface_definition or by a derived_type_definition where the word ABSTRACT appears, the parent type is an abstract interface, and there is no record_extension_part. All primitive operations of an abstract interface type must be declared abstract (or perhaps "is null"?). Only abstract interface types may be mentioned in an abstract_interface_list. A tagged type may declare that it "implements" an abstract interface by mentioning it after the reserved word "NEW" or "AND". Note that a tagged type defined by a derived_type_definition must include a record_extension_part. If an abstract interface mentions other abstract interface types in its definition (after the reserved word NEW or AND), then any type that implements this new interface must also implement these other abstract interfaces. The new abstract interface is also said to "implement" the other interfaces that it mentions. An abstract interface "implements" itself as well. Finally, a tagged type "implements" all of its ancestor tagged types, including itself. Note that we allow an abstract interface type as the parent type of a record extension or a private extension to simplify the syntax, and to allow an abstract interface type and an abstract tagged type to be used in a very similar fashion. This allows one to switch from an abstract tagged type to an abstract interface type during maintenance without significant disruption. [One possible goal would be for us to change Ada.Finalization.[Limited_]Controlled into abstract interface types. This might require us to define an alternative to "is abstract' such as "procedure ... is null" whereby an abstract interface could establish a "null" default for an operation, since that is what Controlled provides fopr all of its operations.] If the reserved word LIMITED appears in an abstract_interface_definition, the abstract interface is an abstract limited interface, and assignment and equality are not available on its classwide type; otherwise it is an abstract nonlimited interface. A nonlimited type may implement an abstract limited interface, but not vice-versa. A derived type is limited if and only if its parent type is limited. A type that implements (directly or indirectly) an abstract interface inherits the interface's (abstract) primitive operations with the usual substitution of the new type for the abstract interface type in their profile. If a type inherits multiple primitive subprograms that are homographs of one another, they must be subtype conformant with one another. Also, a non-abstract inherited subprogram overrides any abstract ones, and if they are all abstract, they must be fully conformant with one another or be overridden [so the formal parameter names and defaults are well-defined]. If a type is non-abstract, and it inherits any abstract primitives that are not overridden by inherited non-abstract ones, it also must (as usual) override them. The 'Class attribute is defined for abstract interface types. The classwide type associated with an abstract interface "covers" all types that implement it, and their associated classwide types. Conversions are permitted between (the classwide type of) any descendant of a type that implements an abstract interface and the classwide type of the abstract interface. Converting to a covered type generally requires a tag check. Membership tests are permitted where the operand's type covers the tested type. Note that only dispatching calls are permitted on the primitives of an abstract interface, since they are all abstract. A tagged type matches a generic formal private extension so long as it implements all the types mentioned in the formal_derived_type_definition. Similarly, if the record extension that completes a private extension declaration must implement all the types mentioned in the private extension declaration. An abstract interface matches a formal derived type without the words "WITH PRIVATE" so long as the word ABSTRACT appears and it implements all the abstract interfaces mentioned in the formal_derived_type_definition. Implementation Model A possible implementation model for an abstract interface reference is to use a pair of pointers, one to an interface-specific dispatch table, and the other to the tagged object of the implementing type. The interface-specific dispatch table would have a layout determined by the order in which the primitives of the abstract interface were declared, while its content would be determined by the particular type implementing the interface. If a given type (including potentially an abstract interface) implements one or more abstract interfaces, appropriate dispatching tables for these other interfaces must be pointed-to from the given type's dispatch table, at known offsets. This allows a conversion from the given type to one of these interfaces to be performed efficiently, by fetching the desired interface-specific pointer from the appropriate slot in the given type's dispatch table. Converting from an abstract interface to a non-interface type that implements it can be performed using the same approach used in Ada 95 to convert to a descendant tagged type, by checking a table of ancestors to see if the target type appears at the appropriate level in the table. On the other hand, converting from one abstract interface to another one that implements it will generally require more overhead, both for the conversion and the associated run-time check (essentially the same logic would be involved in a membership test). One mechanism to support interfact-to-interface conversion is for every tagged type to have in its dispatch table a pointer to an array of all the interfaces it implements, as well as a pointer to a parallel array with the corresponding interface-specific dispatch tables. (This pair of parallel arrays could of course be combined into a single array of pairs.) To convert from one interface to another, one must be sure that the type implements the target interface. This can be done by scanning down this array. If the target interface is found, then the conversion would pick up the corresponding interface-specific dispatch table from the parallel array to form the two-word interface reference. Note that this same array of interface-specific dispatch tables can be used to support the conversion from a tagged classwide type to an interface it is known to implement, by ensuring the array is in the same order in all descendants of a given tagged type, and it is only added to on the end as more interfaces are implemented by lower-down descendants. This conversion would not require any searching, since the relevant offsets would be known at compile-time by suitable ordering of the arrays. **************************************************************** From: Tucker Taft Sent: Monday, December 04, 2000 9:44 AM Subject: Multiple inheritance bug bites ESA For whatever reason, it sounds like ESA now believes multiple inheritance is essential in some situations. Groan. Perhaps the multiple inheritance AI needs a slightly higher profile ;-) -Tuck -------------------- From team-ada@ACM.ORG Mon Dec 4 08:33 EST 2000 MIME-Version: 1.0 Date: Mon, 4 Dec 2000 15:31:46 +0200 From: Soeren Henssel Subject: ESA now also "prefer" C++ instead of Ada on some projects European Space Agency has designed a prototype for component-based software framework for a satellite Attitude and Orbit Control System (AOCS). Their home page is at => http://www.softwareresearch.net/AocsFrameworkProject/ProjectHomePage.html It has been programmed in C++ in preference to Ada 95 - read inter alia http://www.softwareresearch.net/AocsFrameworkProject/DesignPrinciples.html paragraph "Language Compatibility" for the reasons behind the decission. The main reason is lack of multiple inheritance in Ada 95. **************************************************************** From: Randy Brukardt Sent: Monday, December 04, 2000 10:31 PM While editing Tucker's proposal, I was struck by the following: > Note that we allow an abstract interface type > as the parent type of a record extension or a private extension > to simplify the syntax, and to allow an abstract interface type and > an abstract tagged type to be used in a very similar fashion. > This allows one to switch from an abstract tagged type to an > abstract interface type during maintenance without significant > disruption. [One possible goal would be for us to change > Ada.Finalization.[Limited_]Controlled into abstract interface > types. This might require us to define an alternative to "is abstract' > such as "procedure ... is null" whereby an abstract interface > could establish a "null" default for an operation, since that is > what Controlled provides fopr all of its operations.] This "goal" seems to me to be a good way to kill off this proposal. The effect of this would be to require a significant change in the way that finalization is implemented. For instance, if the implementation literally uses a list of Ada.Finalization.Controlled'Class to implement this, the pointer size would change (assuming the use of the implementation model given in the AI). Similarly, the implementation would need to handle any additional data needed for finalization by some compiler magic, rather than by inheritance as is currently done (assuming that abstract interface types may not have components). I note that the proposal never seems to state that abstract interface types must not define any components. Certainly, the implementation model and the entire discussion seem to assume that is the case. (An "normal" abstract type can have components defined, they'll be part of any extension; but I don't think we want that here.) **************************************************************** From: Tucker Taft Sent: Wednesday, December 20, 2000 5:51 PM Randy Brukardt wrote: > While editing Tucker's proposal, I was struck by the following: > ... [One possible goal would be for us to change > > Ada.Finalization.[Limited_]Controlled into abstract interface > > types. This might require us to define an alternative to "is abstract' > > such as "procedure ... is null" whereby an abstract interface > > could establish a "null" default for an operation, since that is > > what Controlled provides fopr all of its operations.] > > This "goal" seems to me to be a good way to kill off this proposal. The > effect of this would be to require a significant change in the way that > finalization is implemented. For instance, if the implementation literally > uses a list of Ada.Finalization.Controlled'Class to implement this, the > pointer size would change (assuming the use of the implementation model > given in the AI). Similarly, the implementation would need to handle any > additional data needed for finalization by some compiler magic, rather than > by inheritance as is currently done (assuming that abstract interface types > may not have components). I realized this was a possibility, but I wasn't sure what current implementation strategies actually are. Comments from vendors about specific implementation problems with this possible change would certainly be of interest. In any case, I certainly hope that the decision whether to use abstract interfaces for Ada.Finalization.Controlled can be viewed as a completely separate issue. I would suspect that there are other similar situations, even if Finalization turns out not to work for other reasons, where one would want to move from abstract tagged type to abstract interface or vice-versa during maintenance or enhancement. That is one of the main advantages of the proposed syntax, and is why I changed the suggested syntax from what I presented in Baltimore. > I note that the proposal never seems to state that abstract interface types > must not define any components. Certainly, the implementation model and the > entire discussion seem to assume that is the case. (An "normal" abstract > type can have components defined, they'll be part of any extension; but I > don't think we want that here.) I improperly presumed the reader knew what was meant by an abstract "interface" type. It definitely doesn't have any data components. That is one of the things that significantly simplifies multiple inheritance of interfaces. I also notice that an example is missing. I will try to provide one over the next couple of days. **************************************************************** From: Steve Baird Sent: Thursday, May 16, 2002 2:38 PM This is an initial proposal for wording for AI-00251 and some related issues (null procedures and unreserved keywords). -- Steve ---------------------------------------------------------------- ---------------------------------------------------------------- Null procedures: 3.1 Add basic_declaration ::= ... | null_procedure_declaration -------- 6.1 Replace subprogram_specification ::= PROCEDURE defining_program_unit_name parameter_profile with procedure_specification ::= PROCEDURE defining_program_unit_name parameter_profile subprogram_specification ::= procedure_specification -------- 6.7 Null Procedures Syntax null_procedure_declaration ::= procedure_specification IS NULL; Static Semantics A null_procedure_declaration declares a null procedure. Dynamic Semantics The execution of a null procedure is invoked by a subprogram call. This execution has no effect. -------- Discussion: 1) Like an instantiation, a null procedure is not allowed as a completion. Allowing this would double the amount of RM text needed. 2) A null procedure may have OUT-mode parameters, the same as a conventional "begin null; end;" procedure. 3) Change Ada.Finalization spec to declare Initialize/Finalize/Adjust as null procedures? ---------------------------------------------------------------- ---------------------------------------------------------------- Unreserved keywords (see AI-284): 2.2(1) Replace "a reserved word," with "a keyword," 2.3(4) Replace An identifier shall not be a reserved word. with An identifier shall not be a reserved keyword. Replace 2.9 with: 2.9 Keywords Syntax The following are the reserved keywords (ignoring upper/lower case distinctions): ABORT ... XOR [same list as before] The following are the unreserved keywords: INTERFACE Throughout the RM: Replace "reserved word" with "keyword" (e.g. 3.9.3(2)). ---------------------------------------------------------------- ---------------------------------------------------------------- Abstract Interface Types 3.2.1 Add type_definition ::= ... | abstract_interface_definition -------- 3.4(2) Replace derived_type_definition ::= [ABSTRACT] NEW parent_subtype_indication [record_extension_part] with derived_type_definition ::= [ABSTRACT] NEW parent_subtype_indication [[AND abstract_interface_list] record_extension_part] -------- 3.4 Add after paragraph 23: A type which implements an abstract interface (see 3.9.4) inherits subprograms from the interface type, in the same way as for a type derived from the interface type. -------- 3.9.1 Add between sentences 3 and 4 of paragraph 3: The accessibilty level (see 3.10.2) of a type which implements an abstract interface (see 3.9.4) shall not be statically deeper than that of the interface type. Add after sentence 1 of paragraph 4: An abstract interface shall not be implemented in a generic body if the interface type is declared outside that body. -------- 3.9.3 Add after paragraph 6: Corresponding rules apply for a type which inherits an abstract subprogram by implementing an abstract interface (see 3.9.4, 3.4). -------- 3.9.4 Abstract Interface Types An abstract interface type is an abstract tagged type intended for use in providing a restricted form of multiple inheritance. A tagged type may "implement" multiple interfaces, thereby allowing multiple views of objects of the type. Syntax abstract_interface_definition ::= ABSTRACT [LIMITED] INTERFACE abstract_interface_list ::= abstract_interface_subtype_mark {AND abstract_interface_subtype_mark} Legality Rules An abstract interface type (also called an "interface type" or "interface") is a specific abstract tagged type that is defined by an abstract_interface_definition, or by a derived_type_definition or formal_derived_type_definition where the keyword ABSTRACT appears and the parent type is an interface type. An interface type shall have no components. All primitive operations of an interface type shall be abstract subprograms or null procedures. The type of a subtype named in an abstract_interface_list shall be an interface type. A descendant of a type which names an interface type in an abstract_interface_list, or which is derived from an interface type, is said to "implement" the interface and all of its ancestors. A class-wide type implements the interfaces implemented by the corresponding specific type. The corresponding full view of a partial view of a type implements all interfaces implemented by the partial view. A type which implements an interface shall be limited if and only if the interface type is limited. If a partial view defines an interface type, then the corresponding full view shall define an interface type. If a full view implements an interface, then the corresponding partial view (if any) shall implement the interface. For an interface type declared in a visible part, a primitive subprogram shall not be declared in the private part. In addition to the places where Legality Rules normally apply (see 12.3), these rules apply also in the private part of an instance of a generic unit. -------- 4.5.2 In paragraph 3, replace: If the tested type is tagged, then the simple_expression shall resolve to be of a type that covers or is covered by the tested type; with If the tested type is tagged, then the simple_expression shall resolve to be of a type that covers or is covered by the tested type, or of a class-wide type which covers an abstract interface type that is implemented by the tested type. -------- 4.6 Add after paragraph 20: If the target type is tagged and there exists no type that is an ancestor of both the target type and the operand type, then either: The target type shall cover or be an ancestor of an abstract interface type which is implemented by the operand type; or The operand type shall be a class-wide type that covers an abstract interface type which is implemented by the target type. In paragraph 21, replace "four" with "five". -------- 7.3 Replace private_extension_declaration ::= type defining_identifier [discriminant_part] is [ABSTRACT] NEW ancestor_subtype_indication WITH PRIVATE; with private_extension_declaration ::= type defining_identifier [discriminant_part] is [ABSTRACT] NEW ancestor_subtype_indication [[AND abstract_interface_list] WITH PRIVATE]; -------- 8.3 Add after paragraph 12: An implicit declaration of a subprogram which is neither abstract nor a null procedure overrides that of a subprogram simultaneously inherited by implementing an abstract interface. If one subprogram overrides another and a type simultaneously inherits subprograms corresponding to both, then the inherited subprogram corresponding to the overriding subprogram overrides the other inherited subprogram. If an inherited subprogram corresponds to another subprogram and a type simultaneously inherits subprograms corresponding to both, then the subprogram corresponding to the inherited subprogram overrides the other inherited subprogram. Add after paragraph 23: If two non-overridden homographs are simultaneously inherited as primitive operations of a tagged type which implements an abstract interface, then they shall be fully conformant and either both shall be abstract subprograms or both shall be null procedures. -------- 12.5.1 Replace formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [WITH PRIVATE] with formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [[AND abstract_interface_list] WITH PRIVATE] Add after paragraph 18: If the formal type is an abstract interface type, then the actual type shall be an abstract interface type. The actual type shall implement any interfaces that are implemented by the formal type. -------- 13.14 Add after paragraph 7: The implementation of an abstract interface causes freezing of the abstract interface type. -------- Discussion: 1) The "no components for interface types" rule does not conflict with the "every tagged object has a tag" rule. 13.5.1(15) might be interpreted to mean that a tag is an "implementation-defined component" of a tagged type, but apparently an "implementation-defined component" is not a "component" (see, for example, the definition of "needed components" for record aggregates). 2) An abstract interface type is an abstract tagged type; this point was unclear in the AI. The class-wide type associated with an interface type is not an interface type. 3) It is intended that explicitly specifying that a type implements an interface which it already implements should have no semantic effect. 4) A non-interface type may be derived from an interface type. 5) An abstract non-interface type cannot be directly derived from an interface type. The syntax given in the AI for doing this would violate the rule that an extension part is given as part of a derivation if and only if the parent type is tagged. I feel that preserving this rule is important. I don't want to take a clean, simple rule which users encounter frequently and amend it with an obscure exception. If it is felt that direct derivation of an abstract non-interface type from an an interface is an important capability that must be supported, then alternative syntax should be considered. Even an interminable string of keywords like type T is abstract new Some_Interface_Type and not interface with null record; would be preferable to the AI's proposal; perhaps the "and not Interface" part could be omitted in the case where a non-empty list of components is given. 6) It is intended that if two homographs are inherited as primitive operations of a tagged type, then they can share one slot in the dispatch table. 7) No formal abstract interface types. Use formal abstract tagged private (or formal derived) instead. A type declared in the generic package spec can't implement a formal tagged private, but that's ok. The situation today with deriving a non-abstract type from a formal abstract type is already quite ugly (the constract model does not include any way to specify the set of abstract operations that must be overridden, so that check is deferred until the instantiation) - we don't want to shine a spotlight on this corner of the language. 8) The rule that only a tagged type may implement interfaces is not stated explicitly; it is implicit in the syntax. 9) There are interactions between this AI and AI-279. In particular, it is ok for Some_Abstract_Interface_Type'Class'Input to read in the tag of a type which implements the interface but is not derived from it. The rule given in AI-279 requires that Constraint_Error be raised in this case. This check should be relaxed. 10) A primitive subprogram is inherited or it is not; it does not matter if there is more than one reason for its inheritance - only one inherited subprogram results. Does this need to be stated explicitly? 11) The term "implement" is defined differently than in the AI. An interface does not implicitly implement itself (because then it would inherit operators from itself). 12) Nothing here (in particular, nothing in the 8.3 stuff) is intended to change the semantics of a program which declares no interface types. 13) An example to illustrate some of the 8.3 stuff: package P1 is type T1 is abstract interface; procedure P (X : T1) is abstract; -- P'1 end P1; package P2 is type T2 is abstract new P1.T1; -- P'2 (implicit) procedure P (X : T2) is abstract; -- P'3 end P2; type D is new Some_Tagged_Type and P1.T1 and P2.T2 with null record; D inherits procedures P'4, P'5, and P'6, corresponding to P'1, P'2, and P'3, respectively. Since P'3 overides P'2, P'6 overrides P'5 by the "overriding is preserved by inheritance" rule. Since P'2 corresponds to P'1, P'5 overrides P'4 by the "inherited copies of your parent's ops hide inherited copies of your grandparent's ops" rule. The word "simultaneous" is used in contradistinction to the word "previous" in 8.3(12). The word "corresponds" is used as "corresponding" is used in 3.4(17). 14) Is this section of the proposed 8.3 addition, If one subprogram overrides another and a type simultaneously inherits subprograms corresponding to both, then the inherited subprogram corresponding to the overriding subprogram overrides the other inherited subprogram. , necessary, or can it be derived from existing language rules? 15) Allowing implementation of a limited interface by a non-limited type might be feasible, but it would introduce some complications: A (dispatching) caller of a primitive function of a limited interface type returning that type would not know statically whether the (non-abstract) callee has a return-by-reference result type. For replicated-generic implementations, this would be something new. The caller of such a function would need to finalize the function result if and only if the function call introduces an anonymous object (see 7.6.1(13/1))); this would no longer be known statically. Strictly from a user's perspective (i.e. ignoring any implementation problems), would support for this construct even be desirable? The problems associated with disallowing this construct in the face of limited views of non-limited types (i.e. a limited formal type where the corresponding actual type is non-limited) are no worse than for 3.7(1)'s rule prohibiting access discriminants for non-limited types. 16) Does the list of legality rules which need to be checked in the private part of an instance (see 12.3(11), AARM 12.3(11.y)) need to be extended? (The change to 3.9.1(3) is one such extension; the last paragrah of 3.9.4 is another). 17) Recommended level of support for representation items for interface types = "confirmation only" ? Or perhaps no representation items at all for interface types - only operational items; would this be a legality rule or just the recommended level of support? 18) Should the AI include a sample implementation model for My_Interface_Type'Class'Input ? 19) Are there interactions between interface types and unknown discriminants that need to be addressed? I don't see any. 20) Yes, it really is illegal if the parent type of a "with private" type's completion happens to implement some interface that the private view did not. This may turn out to be a pain. Relaxing the rule that a partial view must implement all interfaces implemented by the completion is not out of the question, but it could open the door for some very peculiar situations. If this example, package P is packge Pkg is type Ifc is abstract interface; procedure Foo (X : Ifc) is abstract; end; type Parent_1 is tagged null record; type T1 is new Parent_1 with private; private type Parent_2 is new Parent_1 and Pkg.Ifc with null record; procedure Foo (X : Parent_2); -- Foo #1 type T1 is new Parent_2 with null record; end; with P; package P_Client is type T2 is new P.T1 and P.Pkg.Ifc with null record; procedure Foo (X : T2); -- Foo #2 X : T2; end P2_Client; with P_Client; package body P is ... begin Pkg.Foo (Pkg.Ifc'Class (P_Client.X)); -- call Foo #2 Pkg.Foo (Pkg.Ifc'Class (T1 (P_Client.X))); -- call Foo #1 end P2; , were legal (it is illegal because the completion of T1 implements an interface that is not implemented by the the partial view), then we would have two dispatching calls to Pkg.Foo with the two controlling operands having the same tag and yet different bodies would be executed. The two conversions to Pkg.Ifc'Class would map Pkg.Foo to different slots in the same dispatch table because the source types of the conversions are different. **************************************************************** From: Randy Brukardt Sent: Thursday, May 16, 2002 11:31 PM > Unreserved keywords (see AI-284): Are you taking over this AI? It needs a discussion section at least. > Throughout the RM: > Replace "reserved word" with "keyword" (e.g. 3.9.3(2)). NO, NO, NO! At the meeting, Bob pointed out that there are 96 occurrences of this term in the standard. We can't make 94 text changes in the Amendment, people will reject it as being too large without even seeing what's new. Besides, it would take me at least 6 hours to do this, and I think I'd demand extra pay to compensate for the extreme boredom involved. (Each paragraph would have to be cut from the standard and properly formatted.) Plus the tools would need to be updated to handle many, many changes from a single AI. At the very least, you have to enumerate every place that needs to be changed. That would be necessary to help ensure that I don't miss any. The minutes say "Tucker suggests the age-old solution of defining "reserved word" to be equivalent to "reserved keyword". In the more general case, we simply say "keyword". I'm not adverse to making this change in paragraphs that we are changing anyway (there are already five such uses, four of which are new). But, please, lets stick with what we decided. We simply don't have the budget to spend on unnecessary massaging. ... > -------- > > 3.9.3 > > Add after paragraph 6: > > Corresponding rules apply for a type which inherits an abstract > subprogram by implementing an abstract interface (see 3.9.4, 3.4). Oh-oh! Do we have to define "corresponding" here? :-) :-) **************************************************************** From: Steve Baird Sent: Friday, May 17, 2002 2:25 PM > Are you taking over this AI? No (or at least I didn't think that I was). I just wanted to provide enough context so that I could talk about the keyword INTERFACE in the AI-00251 writeup. > The minutes say "Tucker suggests the age-old solution of > defining "reserved word" to be equivalent to > "reserved keyword". In the more general case, we > simply say "keyword". Fine with me. **************************************************************** From: Tucker Taft Sent: Friday, May 17, 2002 10:52 AM Looks good. I would suggest we define "implement" more generally, so that a derived type "implements" its parent type. Then we can define "cover", etc. in terms of "implement" without any special cases. **************************************************************** From: Steve Baird Sent: Monday, May 20, 2002 1:32 PM This sounds like a good approach. This could solve a problem with the initial proposal, where explicit conversion is required in cases where we probably want to allow implicit conversion. Given this example type Ifc is abstract interface; type T is new Some_Tagged_Type and Ifc with null record; type Ifc_Ref is access all Ifc'Class; X : aliased T; Ptr1 : Ifc_Ref := Ifc'Class (X)'Access; -- legal Ptr2 : Ifc_Ref := X'Access; -- legal ? , I believe that the initial proposal would allow the first use of the Access attribute, but not the second. I think that we want to allow the second use. **************************************************************** From: Tucker Taft Sent: Monday, May 20, 2002 1:40 PM Yes, I agree that Ifc'Class should "cover" T and its derivatives. I believe that the current AI implies that, but that your proposed wording does not. **************************************************************** From: Steve Baird Sent: Wednesday, June 5, 2002 3:49 PM Tuck says: > I would suggest we define "implement" more generally, > so that a derived type "implements" its parent type. Then we can > define "cover", etc. in terms of "implement" without any special cases. I agree that if type T implements some interface type Ifc, then we want to define "cover" so that Ifc'Class covers T. I'm not so sure about changing the definition of "implement". It would be nice to use a generalized form of "implementation" for determining operator inheritance - a type inherits operators from the types that it implements, and from no others. This rule would imply that a derived type inherits operators not only from its parent, but from all of its parent's ancestors. Typically this extra inheritance would make no difference (all the extra inherited operators would be overridden), but this would be an incompatible change if an ancestor has primitive subprograms that the parent lacks (this can only happen in the untagged case): package Pkg is type T1 is null record; type T2 is new T1; procedure P (X : T1); type T3 is new T2; -- T3 should not inherit a P procedure end Pkg; With that in mind, here is a revision of my previous attempt at wording for AI-251. -- Steve ---------------------------------------------------------------- ---------------------------------------------------------------- 3.4.1(9) Add after the first sentence: If a type T1 implements an abstract interface type T2 (see 3.9.4), then T2'Class also covers all types covered by T1'Class. -------- 4.5.2: Ignore the changes I proposed earlier; revert to the current RM text. -------- 4.6: Ignore the changes I proposed earlier. Replace paragraphs 21-23 with: If the target type is tagged, then either: The operand type shall be covered by or descended from the target type; or The operand type shall be a class-wide type that covers the target type. If the target type is not included in any of the above five cases, there shall be a type that is an ancestor of both the target type and the operand type. **************************************************************** From: Steve Baird Sent: Thursday, June 13, 2002 3:52 PM I intended that the "implements" relation be transitive, but I didn't word the definition correctly. In this example, type Ifc1 is abstract interface; type Ifc2 is abstract interface; type Ifc3 is abstract new Ifc1 and Ifc2 with null record; type T1 is tagged null record; type T2 is new T1 and Ifc3 with null record; , T2 implements Ifc3 and Ifc3 implements Ifc2, so therefore T2 should implement Ifc2. I believe that the proposed wording should be fixed to handle this case. In the new 3.9.4 section, replace A descendant of a type which names an interface type in an abstract_interface_list, or which is derived from an interface type, is said to "implement" the interface and all of its ancestors. with A descendant of a type which names an interface type in an abstract_interface_list, or which is derived from an interface type, is said to "implement" the interface and any other interfaces implemented by that interface. Comments? **************************************************************** From: Steve Baird Sent: Wednesday, September 25, 2002 1:12 PM This is my attempt at incorporating the comments of the Vienna meeting into AI-251. Changes include: 1) "abstract interface" => "interface" in both terminology and syntax. 2) A derived type is never an interface type, as described in the Vienna meeting minutes. To get the effect of derivation, declare a second interface type which implements the first one. 3) The notion of implicit declarations being declared "simultaneously" is replaced with declarations occurring implicitly "at the same point". 4) Formal interface types are added. 5) A different way of expressing the notion that if a routine inherits two null homographs or two abstract homographs, then there is really no ambiguity. 6) Expand the definition of "cover" so that Some_Interface_Type'Class covers all types which implement the interface. 7) Define the "implements" relation to be transitive. I'm not at all sure that these are all improvements, but I believe/hope that they reflect the consensus of the group. See also the discussion points at the end. -- Steve ---------------- Interface Types 3.2.1 Add type_definition ::= ... | interface_type_definition -------- 3.4 Add after paragraph 23: A type which implements an interface type (see 3.9.4) inherits subprograms from the interface type, in the same way as for a type derived from the interface type. -------- 3.4.1(9) Add after the first sentence: If a type T1 implements an interface type T2 (see 3.9.4), then T2'Class also covers all types covered by T1'Class. -------- 3.9.1 In paragraph 2, replace record_extension_part ::= WITH record_definition with record_extension_part ::= WITH [interface_list AND] record_definition . Add between sentences 3 and 4 of paragraph 3: The accessibilty level (see 3.10.2) of a type which implements an interface type (see 3.9.4) shall not be statically deeper than that of the interface type. Add after sentence 1 of paragraph 4: An interface type shall not be implemented in a generic body if the interface type is declared outside that body. -------- 3.9.3 Add after paragraph 6: Corresponding rules apply for a type which inherits an abstract subprogram by implementing an interface type (see 3.9.4, 3.4). -------- 3.9.4 Interface Types An interface type is an abstract tagged type intended for use in providing a restricted form of multiple inheritance. A tagged type may "implement" multiple interfaces, thereby allowing multiple views of objects of the type. Syntax interface_type_definition ::= [LIMITED] INTERFACE [WITH interface_list] interface_list ::= interface_subtype_mark {AND interface_subtype_mark} Legality Rules An interface type (also called an "interface") is a specific abstract tagged type that is defined by an interface_type_definition. An interface type shall have no components. All primitive operations of an interface type shall be abstract subprograms or null procedures. The type of a subtype named in an interface_list shall be an interface type. A descendant of a type which names an interface type in an interface_list, or which is derived from an interface type, is said to "implement" the interface and any other interfaces implemented by that interface. A class-wide type implements the interfaces implemented by the corresponding specific type. The corresponding full view of a partial view of a type implements all interfaces implemented by the partial view. A type which implements an interface shall be limited if and only if the interface type is limited. If a partial view defines an interface type, then the corresponding full view shall define an interface type. If a full view implements an interface, then the corresponding partial view (if any) shall implement the interface. For an interface type declared in a visible part, a primitive subprogram shall not be declared in the private part. In addition to the places where Legality Rules normally apply (see 12.3), these rules apply also in the private part of an instance of a generic unit. -------- 4.6 Replace paragraphs 21-23 with: If the target type is tagged, then either: The operand type shall be covered by or descended from the target type; or The operand type shall be a class-wide type that covers the target type. If the target type is not included in any of the above five cases, there shall be a type that is an ancestor of both the target type and the operand type. -------- 7.3 Replace private_extension_declaration ::= type defining_identifier [discriminant_part] is [ABSTRACT] NEW ancestor_subtype_indication WITH PRIVATE; with private_extension_declaration ::= type defining_identifier [discriminant_part] is [ABSTRACT] NEW ancestor_subtype_indication WITH [interface_list AND] PRIVATE; -------- 8.3 Add after paragraph 12: An implicit declaration of an inherited subprogram which is neither abstract nor a null procedure overrides that of a subprogram inherited by implementing an interface which is implicitly declared at the same point. If one subprogram overrides another and a type inherits subprograms corresponding to both which are implicitly declared at the same point, then the inherited subprogram corresponding to the overriding subprogram overrides the other inherited subprogram. If an inherited subprogram corresponds to another subprogram and a type inherits subprograms corresponding to both which are implicitly declared at the same point, then the subprogram corresponding to the inherited subprogram overrides the other inherited subprogram. If a type which implements an interface inherits two or more fully conformant abstract subprograms, or two or more fully conformant null subprograms, which are implicitly declared at the same point, and if the preceding rules would not specify any overriding relationships among these subprograms, then the implementation shall select one of these subprograms and it shall override the others. -------- 12.5 Add formal_type_declaration ::= ... | formal_interface_type_definition -------- 12.5.1 Replace formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [WITH PRIVATE] with formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [WITH [interface_list AND] PRIVATE] Add after paragraph 18: The actual type shall implement any interfaces that are implemented by the formal type. -------- 12.5.5 Formal Interface Types The class determined for a formal interface type is the class of all interface types. Syntax formal_interface_type_definition ::= interface_type_definition Legality Rules The actual type shall implement any interfaces that are implemented by the formal type. The actual type shall be limited if and only if the formal type is limited. -------- 13.14 Add after paragraph 7: The implementation of an interface causes freezing of the interface type. -------- Discussion: 1) A derived type is never an interface type. One can derive from an interface type, but the result is not an interface. Instead, define a new interface type which implements the "parent" interface. Similarly, a classwide type is never an interface type. 2) We try to avoid having one section of the manual override another. Is the definition An interface type (also called an "interface") is a specific abstract tagged type that is defined by an interface_type_definition. "bad" in that sense? See 3.9.2(2) (the definition of a tagged type) and 3.9.3(2) (the definition of an abstract type). 3) It is intended that in the case where the implementation arbitrarily chooses one overrider from among group of inherited subprograms, users should be unable to determine which member was chosen. In particular, users should be unable to do this by means of the Access (or Unchecked_Access) attribute. In order to accomplish this, and for other reasons, 4.5.2(13)'s rules about access-to-subprogram equality need to be loosened further to allow implementation-dependent results in the case of comparing access values which denote distinct null subprograms. That change is outside of the scope of this AI; it belongs in the AI in which null procedures are defined. Note that, formally speaking, the Address attribute poses no such problems. Although the requirements of the marketplace may be much more restrictive, the language itself allows an implementation tremendous freedom with respect to Address attribute values (see AI-173, 13.7.2(5)). 4) 12.5.5 refers to "the class of all interface types". Is there a problem with having a class which is not closed with respect to derivation? As noted above, a derived type is never an interface type. 5) There is no need to explicitly state that an interface type shall not implement itself (just as circular derivations are disallowed). This is a consequence of the rule that a partial view of an interface must implement any interfaces which are implemented by the full view. 6) Recommended level of support for representation items for interface types = "confirmation only" ? Or perhaps no representation items at all for interface types - only operational items; would this be a legality rule or just the recommended level of support? **************************************************************** From: Robert A. Duff Sent: Thursday, September 26, 2002 6:28 PM > This is my attempt at incorporating the comments of the Vienna meeting > into AI-251. ... > I'm not at all sure that these are all improvements, but I believe/hope that > they reflect the consensus of the group. Well, it might be useful to hear your objections... Nitpick: You use "which" where I would use "that" in most cases. (All except after ",".) You use "point" where the RM uses "place". > Discussion: > > 1) A derived type is never an interface type. One can derive from an > interface type, but the result is not an interface. Instead, define > a new interface type which implements the "parent" interface. > Similarly, a classwide type is never an interface type. But interface'Class is legal, right? > 2) We try to avoid having one section of the manual override another. > Is the definition > An interface type (also called an "interface") is a specific > abstract tagged type that is defined by an > interface_type_definition. > "bad" in that sense? See 3.9.2(2) (the definition of a tagged type) > and 3.9.3(2) (the definition of an abstract type). There was a definite attempt in RM95 to put *all* the rules for abstract things together in one section. It wouldn't hurt to preserve that. > 3) It is intended that in the case where the implementation > arbitrarily chooses one overrider from among group of inherited > subprograms, users should be unable to determine which > member was chosen. > > In particular, users should be unable to do this by means of the > Access (or Unchecked_Access) attribute. > > In order to accomplish this, and for other reasons, 4.5.2(13)'s rules > about access-to-subprogram equality need to be loosened further to allow > implementation-dependent results in the case of comparing access values > which denote distinct null subprograms. I never liked that rule, but I guess you're right. > That change is outside of the scope of this AI; it belongs in > the AI in which null procedures are defined. > > Note that, formally speaking, the Address attribute poses no such > problems. Although the requirements of the marketplace may be much > more restrictive, the language itself allows an implementation > tremendous freedom with respect to Address attribute values > (see AI-173, 13.7.2(5)). Right. > 4) 12.5.5 refers to "the class of all interface types". Is there a > problem with having a class which is not closed with respect to > derivation? As noted above, a derived type is never an interface type. Why is it not closed under derivation? Oh, I see, a type derived from an interface is not an interface. Well, yeah, it's bothersome to call that a "class". I don't see an easy way around it. > 5) There is no need to explicitly state that an interface type > shall not implement itself (just as circular derivations are > disallowed). This is a consequence of the rule that a partial > view of an interface must implement any interfaces which are > implemented by the full view. Could use a NOTE. > 6) Recommended level of support for representation items for > interface types = "confirmation only" ? Or perhaps no > representation items at all for interface types - only > operational items; would this be a legality rule or just the > recommended level of support? I'm not sure what you're getting at here. Interfaces have no components, so what's the "representation" issue. Sure, I guess operational items should be allowed.... Is it necessary to say anything? **************************************************************** From: Tucker Taft Sent: Friday, September 27, 2002 4:12 PM > This is my attempt at incorporating the comments of the Vienna meeting > into AI-251. > ... > -------- > 3.9.4 Interface Types > An interface type is an abstract tagged type intended for use > in providing a restricted form of multiple inheritance. > A tagged type may "implement" multiple interfaces, thereby allowing > multiple views of objects of the type. > Syntax > interface_type_definition ::= [LIMITED] INTERFACE [WITH interface_list] > interface_list ::= interface_subtype_mark {AND interface_subtype_mark} > Legality Rules > An interface type (also called an "interface") is a specific abstract > tagged type that is defined by an interface_type_definition. This is not a legality rule. It is static semantics (or simply a definition). > > An interface type shall have no components. This is not a legality rule, so "shall have" => "has". This is the static semantics (since there is no way to specify components, it is silly to make this a legality/"shall" rule). > All primitive operations of an interface type shall be abstract > subprograms or null procedures. > > The type of a subtype named in an interface_list shall be an > interface type. > > A descendant of a type which names an interface type in an > interface_list, or which is derived from an interface type, > is said to "implement" the interface and any other interfaces > implemented by that interface. > A class-wide type implements the interfaces implemented by the > corresponding specific type. The corresponding full view of a > partial view of a type implements all interfaces implemented by > the partial view. More static semantics (no "shalls" in these). > ... **************************************************************** From: Randy Brukardt Sent: Friday, September 27, 2002 8:21 PM Steve Baird wrote: > This is my attempt at incorporating the comments of the Vienna meeting > into AI-251. ... Humm, I don't see any syntax for declaring that an original tagged type implements an interface. There is only syntax for declaring that on an extension. That doesn't seem right, why should it be necessary to declare an extra type in order to say that a new tagged type implements some interface? It also means that a private type which implements an interface cannot be completed by a tagged type that is not an extension. type Root is tagged Window and record ...; I would expect a syntax like: record_type_definition ::= [[abstract] tagged] [limited] [interface_list and] record_definition ... > In order to accomplish this, and for other reasons, 4.5.2(13)'s rules > about access-to-subprogram equality need to be loosened further to allow > implementation-dependent results in the case of comparing access values > which denote distinct null subprograms. > > That change is outside of the scope of this AI; it belongs in > the AI in which null procedures are defined. What are you talking about? THIS is the AI where null procedures are defined! The ARG did not decide to split those out of this AI. We would do that only if there was a substantial chance that this AI would not be adopted while null procedures would be. --- It would also be valuable if the !proposal summary and !discussion sections were updated to reflect the current proposal. They are getting way out of date, and I'm not sure if the implementation models, etc. still apply to this current wording. **************************************************************** From: Tucker Taft Sent: Sunday, September 29, 2002 5:29 PM You just use the type extension syntax, but make the parent type be the interface. This is desirable so that a "normal" abstract tagged type may be changed into an interface type without changing any other code. As with "abstract", a type is not an interface unless the word "interface" appears in its definition. Deriving from an interface does not make the derived type an interface. In fact, derived types are never interfaces. Interfaces are always considered "root" types that happen to implement other interfaces. **************************************************************** From: Randy Brukardt Sent: Monday, September 29, 2002 6:44 PM Humm, that would work, but it would be completely inconsistent and unexpected. Moreover, what you would expect to do has no sensible way to indicate the correct thing to do. Indeed, the RM would be completely devoid on any indication of how to do this (as the derivation rules completely follow from existing language rules, and aren't being modified). My "mental image" of these things is that they are a new kind of item. If you want a type to "implement" an interface, you put the interface into an appropriate interface_list. That is not true in the case of derivation. Moreover, deriving from an interface gives a incorrect view of the (single-inheritance) type hierarchy. Such an derivation makes it appear that the new type is a member of the interface type. That's simply wrong, if the intent is to create a new hierarchy of types that happens to implement some interface. The fact that deriving these give a non-interface type is OK by me, but I view that is something that exists at all simply to avoid contract model problems with interfaces. Essentially, deriving one of these has to do SOMETHING, but its not something that you would ever do in new code. Yes, it's helpful in converting abstract types to interfaces, but I don't see that as something that will happen often (for two reasons: "if it ain't broke, don't fix it", and because most abstract types have concrete primitive operations, simply because they could). We'll have to dicuss this at the meeting. **************************************************************** From: Tucker Taft Sent: Monday, September 30, 2002 8:34 PM I consider it essential that interface types and abstract tagged types are very nearly interchangeable, and that you can go back and forth relatively easily, so long as the interface type is the first one listed after "new". The meaning of 'Class, "covers", implements, etc., should all be such that it makes very little difference in "type NT is new T with ..." whether T is an interface, or an abstract tagged type. At least I see that as an important goal. **************************************************************** From: Randy Brukardt Sent: Monday, September 29, 2002 10:03 PM > I consider it essential that interface types and > abstract tagged types are very nearly interchangeable, > and that you can go back and forth relatively > easily, so long as the interface type is the first > one listed after "new". I don't think this will be possible most of the time, because interfaces allow multiple inheritance, and abstract types allow concrete operations and components. Usually one or the other will be a requirement. > The meaning of 'Class, "covers", implements, etc., should > all be such that it makes very little difference in > "type NT is new T with ..." > whether T is an interface, or an abstract tagged type. There certainly is value in keeping the proposal simple by reusing the abstract rules in the usage of these things. And, as I said, I really don't care what derivation means. What I object to is being required to make some random interface "more important" than the others by being required to derive from it when declaring the root of a type hierarchy. Just because some type happens to implement an interface doesn't make the interface a parent of the type hierarchy. Indeed, one could argue that is the problem with Controlled types. (However, they can't be an interface because they do have components - at least they do in Janus/Ada. Illustrating that point.) **************************************************************** From: Michael F. Yoder Sent: Thursday, October 24, 2002 9:05 AM We can get the benefit of Eiffel's renamings, but without awful syntax, if we allow interface renamings to tweak things in a manner similar to procedure renamings changing parameter names. Something like: interface T2 renames T1 is procedure Name_Collision_On_P (args) renames P; function Oops_F_Has_Homonyms (args) return blah renames F; end interface; Then, change the rule so that homonyms from different interfaces are always illegal. If the user wants to complete two or more homonyms the same way, she can use renamings-as-bodies to complete those she's forced to rename. **************************************************************** From: Michael F. Yoder Sent: Thursday, October 24, 2002 9:31 AM Sorry, I didn't include enough context for this to make sense to those who didn't happen to be at the last meeting. The context is that a tagged type is inheriting from two or more interfaces that have homonyms, so the effect of blending them together isn't clear. There are two cases: (1) The homonyms aren't all mode-conformant to one another: one body can't complete all of them. (2) The homonyms are all mode-conformant, so completing them all is at least possible. There's probably uses for this ability other than avoiding name collisions, e.g.: interface Sunviews_Style_Interface renames X_Style_Interface is... **************************************************************** From: Pascal Leroy Sent: Thursday, October 24, 2002 9:33 AM I see that you believe that the proposal is not complex enough already ;-) I mean, this AI is already extremely complicated, both from the language design standpoint and from the implementation standpoint. So complex in fact that I doubt if it will make it into the final amendment. And even if it does, speaking with my implementer hat, I have a hard time believing that we will make the vast investment needed to support it in our products (do I sound like Robert?). So I think we should strive to simplify the proposal, not add bells and whistles that solve cornercase issues. **************************************************************** From: Pascal Leroy Sent: Thursday, October 24, 2002 10:03 AM I agree that this proposal is way past the complexit threshhold. I don't know if it will make it into the amendment, but I can be pretty sure that folks at ACT are not going to give this one any attention regardless. The danger of course is that if we get piles of proposals of this kind, then a strong argument can and will be made to leave the whole thing alone rather than rock the boat with over-complex stuff. **************************************************************** From: Michael F. Yoder Sent: Thursday, October 24, 2002 1:21 PM >>I see that you believe that the proposal is not complex enough already I thought the proposal was horridly complex also until I sorted through what it actually meant. Also, much apparent complexity came from keeping multiple variations in mind at the same time, only one of which can actually be used for a final proposal. I'm not a big OOP fan, but having a halfway-house technology gets the worst of both worlds: OOP advocates will sniff at it, but you still have the maintenance costs. If that sounds like a "keeping up with Java" argument, well, it probably *is* that sort of an argument. I think exception types fall into the same category. The one instance where I might disagree with Tucker is that I'd prefer that the line between the classes having treeish inclusion and the others (the interfaces) be kept visible, and I would cheerfully buy simplicity by making it more visible. Oh, and finally, I don't expect the issue to be a cornercase issue in practice unless interfaces just aren't used. If it were a cornercase issue, why would the awful syntax of Eiffel's renaming conventions (for solving this problem) ever occur? But perhaps Bob Duff can elaborate about the circumstances in which he saw these things. **************************************************************** From: Tucker Taft Sent: Thursday, October 24, 2002 1:33 PM I think this is much more important for Eiffel because they use inheritance for *everything* (including with/use clauses). Note that Java has no renaming capability, and they are succeeding hansomely. Also, Eiffel has multiple *implementation* inheritance, whereas our proposal is a better fit for Java, which has multiple *interface* inheritance. In Eiffel, you have *code* that you are inheriting potentially from two places, and perhaps that means you more frequently will need to resolve conflicts. With the proposed interfaces, no code is being inherited, which might simplify doing "renaming by editing" ;-). My bottom line is I really don't think renaming is important to making this useful, and Java is my best evidence. In Ada, we get to use result type and defaulted parameters to distinguish as well, whereas in Java, they don't have that flexibility. (Using defaulted but ignored parameters to distinguish things might be a clever hack around name conflicts, for those who really find them serious.) **************************************************************** From: Robert A. Duff Sent: Thursday, October 24, 2002 3:57 PM > But perhaps Bob Duff can elaborate > about the circumstances in which he saw these things. I now believe that the name-clash problem should not be solved. (I argued the opposite at the meeting, but Tucker's speech on the subject convinced me to change my mind.) I think this interfaces AI and the other AI about type stubs are the two most important AI's on the table. People really do choose other languages over Ada because of these two major issues. My evidence is of course anecdotal, as is nearly all evidence in the so-called "science" of Computer Science. I have not done market surveys and the like. Sigh. I don't much care whether these two features make it into the Amendment, but I think it's important to the long-term success of Ada that they get implemented by the major compiler vendors, and in a uniform fashion (the "uniform" part is what standards are all about!). It worries me when I see Robert and Pascal threatening to not implement these. (I do understand the reasons. SofCheck doesn't have tons of funding to do this sort of thing, either, unfortunately.) The "problem" we're discussing is that if you import two interfaces, and get a name clash, you can get "stuck". But the *usual* way to fix the problem is to go edit the source code of one or both interfaces -- change the names so they don't clash. The only time that won't work is when you're importing two libraries, and you can't (or don't want to) change their source. The "problem" can only occur if the interfaces proposal is a great success (widely implemented, and used by many vendors). So I say: let's solve that problem when it happens. I.e. consider any "renaming" bells and whistles as a totally separate AI from the basic interfaces proposal. There is no technical reason to solve the problem *now*. I say, let's agree on a standard for the interfaces thing, and let's implement it in the various compilers. If and when Ada becomes wildly successful, we can worry about the naming-clash problem. Other points: - The naming clash problem really is a corner case: You have to have two independent developers choose the same name, the same number and types of parameters, the same result type, AND DIFFERENT MODES. - Any solution to the "problem" that I can think of is totally independent from the basic interfaces proposal -- so there's no reason to include it now. As they say, let's burn that bridge when we come to it. - The interfaces proposal should be kept as simple as possible, so that vendors may find it feasible to implement on limited budgets. - Java doesn't address the "problem", and does just fine. - I don't agree with Mike that OOP advocates will "sniff at" the interfaces stuff without a solution to the naming clash problem. For all these reasons, I am opposed to solving the naming clash problem as part of this AI. There's no harm in creating another AI to address the problem, but *that* AI should be voted in 2015 or so. **************************************************************** From: Robert A. Duff Sent: Thursday, October 24, 2002 4:15 PM > I thought the proposal was horridly complex also until I sorted through > what it actually meant. Also, much apparent complexity came from keeping > multiple variations in mind at the same time, only one of which can > actually be used for a final proposal. I agree with that. The proposal represents non-trivial implementation effort, but it's not *that* bad. It always seems worse when you're weighing 17 possible solutions to the same problem. But I still say: let's keep it as simple as possible. (But no simpler. ;-)) **************************************************************** From: Michael F. Yoder Sent: Thursday, October 24, 2002 6:11 PM >- I don't agree with Mike that OOP advocates will "sniff at" the > interfaces stuff without a solution to the naming clash problem. Just to set the record straight, I didn't say that; this is combining a phrase into a different context. What I believe is that OOP advocates will sniff at Ada if it has no multiple-inheritance mechanism that they choose to regard as real. (There doesn't seem to be much enthusiasm for the generic mixin mechanism et al., and I don't entirely blame them.) Java seems to have established interfaces as real, and they don't break the good properties (e.g. time-boundedness) of the single-inheritance model, at least not for operations on the treeish classes. **************************************************************** From: Robert Dewar Sent: Thursday, October 24, 2002 7:20 PM Hmmm, this seems dubious to me. Why do you believe this? **************************************************************** From: Robert A. Duff Sent: Thursday, October 24, 2002 4:15 PM > Robert A Duff wrote: > > >- I don't agree with Mike that OOP advocates will "sniff at" the > > interfaces stuff without a solution to the naming clash problem. > > > Just to set the record straight, I didn't say that; this is combining a > phrase into a different context. Sorry if I misquoted you. >... What I believe is that OOP advocates > will sniff at Ada if it has no multiple-inheritance mechanism that they > choose to regard as real. (There doesn't seem to be much enthusiasm for > the generic mixin mechanism et al., and I don't entirely blame them.) > Java seems to have established interfaces as real, and they don't break > the good properties (e.g. time-boundedness) of the single-inheritance > model, at least not for operations on the treeish classes. I agree with that. So the question is, will the lack of a solution to the naming clash problem cause folks to scorn our multiple-inheritance of interfaces solution? I think not. **************************************************************** From: Robert I. Eachus Sent: Thursday, October 24, 2002 7:50 PM >I now believe that the name-clash problem should not be solved. >(I argued the opposite at the meeting, but Tucker's speech on the >subject convinced me to change my mind.) I think the problem is actually a bit larger than you think, but I don't think that it is anything that the ARG, or WG9 can or should solve. There are many cases where existing languages will see a clash and Ada won't. Great. There is one case where Ada in my opinion should see a problem and alert the user at compile time. For example, let's say you are inheriting from two different container types. One has an Append with an in out parameter, the parameter of the other is mode in. Forget for a moment all the philosophical and design issues which could result in choosing one or the other. There is a design problem here, and the right thing for the compiler to do is to tell the user, not paper it over. If the user concludes that the two interfaces are not compatible? He is right. There are ugly workarounds, but they should be ugly. The other case that hasn't been discussed as much, but has been mentioned. It is where you have say two Append procedures, one with a single parameter and one that adds a position parameter with a default. It is possible to inherit from both interfaces, but the only way to actually call the one parameter version is to rename it. This is currently one of the more unexpected ;-) features of Ada when it pops up. But again there is no reason the ARG should try to "fix" anything. If through generic instantiation and/or use clauses a call becomes ambiguous for this reason, the programmer has to do real work to decide between calls which should go to the one parameter version, and calls which should use the default. Shrug. The compiler can only alert the programmer to potential problems, it can't read his mind. **************************************************************** From: Steve Baird Sent: Tuesday, January 28, 2003 11:58 AM This is my attempt at incorporating the comments of the Bedford meeting into AI-251. Changes include: 1) New type declaration syntax. 2) New derivation model (a type no longer "implements" an interface; it really is "derived" from the interface type, introducing a restricted notion of multiple parents for a derived type). 3) New membership test, conversion rules. 4) New homograph overriding requirements. See also the discussion section at the end. The last time I sent mail to the ARG list, it somehow ended up in HTML format. I hope that problem has been straightened out, but my apologies in advance if this message ends up similarly mangled. -- Steve ---------------- 3.2.1 Type Declarations Add to syntax type_definition ::= ... | interface_type_definition 3.4 Derived Types and Classes Replace syntax section with interface_list ::= interface_subtype_mark {AND interface_subtype_mark} derived_type_definition ::= [ABSTRACT] NEW parent_subtype_indication [[AND interface_list] record_extension_part] Add at the end of paragraph 3: A derived type has a parent type and zero or more interface parent types. Replace paragraph 8 with: Each class of types that includes the parent type or an interface parent type also includes the derived type. Add after paragraph 25: If a type declaration names an interface type in an interface list, then the declared type inherits any user-defined primitive subprograms of the interface type in the same way. Note: this includes some declarations of non-derived types. 3.4.1 Derivation Classes Insert after the first sentence of paragraph 2: A derived type or interface type is also derived from each of its interface parent types, if any. Replace the last sentence of paragraph 10 with: The ultimate ancestor of a type is the ancestor of that type, if any, that is not a descendant of of any other type and that is not an interface type (see 3.9.4). 3.9.3 Abstract Types and Subprograms Replace paragraphs 4-5 with If a type inherits a subprogram corresponding to an abstract subprogram or to a function with a controlling result, then - If the inheriting type is abstract or untagged, the inherited subprogram is abstract. 3.9.4 Interface Types This section is entirely new. An interface type is an abstract tagged type intended for use in providing a restricted form of multiple inheritance. A tagged type may be derived from multiple interface types, thereby allowing multiple views of objects of the type. Syntax interface_type_definition ::= [LIMITED] INTERFACE [WITH interface_list] Legality Rules An interface type (also called an "interface") is a specific abstract tagged type that is defined by an interface_type_definition. An interface type shall have no components. All user-defined primitive subprograms of an interface type shall be abstract subprograms or null procedures. The type of a subtype named in an interface_list shall be an interface type. If a type declaration names an interface type in an interface list, then the accessibility level of the declared type shall not be statically deeper than that of the interface type; also, the declared type shall not be declared in a generic body if the interface type is declared outside that body. A descendant of an interface type shall be limited if and only if the interface type is limited. A full view shall be a descendant of an interface type if and only if the corresponding partial view (if any) is also a descendant of the interface type. For an interface type declared in a visible part, a primitive subprogram shall not be declared in the private part. In addition to the places where Legality Rules normally apply (see 12.3), these rules apply also in the private part of an instance of a generic unit. 4.5.2 Relational Operators and Membership Tests Name Resolution Two types are said to "potentially share descendants" either if one covers the other or if both are class-wide and the corresponding specific type associated with at least one is an interface type. The simple_expression shall resolve to be of a type that potentially shares descendants with the tested type. 4.6 Type Conversions If the target type is tagged then either: - The operand type shall be covered by or descended from the target type; or - The operand type shall be a class-wide type that covers the target type; or - The operand and target types shall potentially share descendants. If the target type is not included in any of the above five cases, there shall be a type that is an ancestor of both the target type and the operand type. 7.3 Private Types and Private Extensions Replace private_extension_declaration ::= TYPE defining_identifier [discriminant_part] IS [ABSTRACT] NEW ancestor_subtype_indication WITH PRIVATE; with private_extension_declaration ::= TYPE defining_identifier [discriminant_part] IS [ABSTRACT] NEW ancestor_subtype_indication WITH [interface_list AND] PRIVATE; 8.3 Visibility Add after paragraph 12: An implicit declaration of an inherited subprogram which is neither abstract nor a null procedure overrides that of a subprogram inherited from an interface type which is implicitly declared at the same point. If one subprogram overrides another and a type inherits subprograms corresponding to both which are implicitly declared at the same point, then the inherited subprogram corresponding to the overriding subprogram overrides the other inherited subprogram. If an inherited subprogram corresponds to another subprogram and a type inherits subprograms corresponding to both which are implicitly declared at the same point, then the subprogram corresponding to the inherited subprogram overrides the other inherited subprogram. Add after paragraph 26: If a descendant of an interface type inherits two homographs which are implicitly declared at the same point, then both shall be overridden. Note: this requirement may be impossible to satisfy if the homographs are not mode-conformant. 12.5 Formal Types Add to syntax formal_type_declaration ::= ... | formal_interface_type_definition 12.5.1 Formal Private Types Replace formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [WITH PRIVATE] with formal_derived_type_definition ::= [ABSTRACT] NEW subtype_mark [WITH [interface_list AND] PRIVATE] Add after paragraph 15: The actual type shall be a descendant of every ancestor of the formal type. 12.5.5 Formal Interface Types This section is entirely new. The class determined for a formal interface type is the class of all interface types. Syntax formal_interface_type_definition ::= interface_type_definition Legality Rules The actual type shall be a descendant of every ancestor of the formal type. The actual type shall be limited if and only if the formal type is limited. 13.14 Freezing Rules Add after paragraph 7: The declaration of a specific descendant of an interface type freezes the interface type. -------- Discussion: 1) The compatibility rules for membership testing could be tightened to filter out more cases where the result of a membership test could not possibly be True. The two types could be required to agree with respect to limitedness, accessibility level, and/or set of enclosing generic bodies (with a recheck generic spec rule as well). Do we want all this in a name resolution rule? I'd say no. 2) "Potentially share descendants" seems like an awkward term. How about "weakly compatible"? "Potentially intersecting"? "Potentially convertible"? The idea here is that we want to allow any membership tests where the answer is not known statically. This includes a case like type T1 is tagged null record; type T2 is interface; X1 : T1'Class := ... ; X2 : T2'Class := ... ; Flag : Boolean := (X1 in T2'Class) and (X2 in T1'Class); because of the possibility of a type which is descended from both T1 and T2, e.g. type T3 is new T1 and T2 with null record; . 3) It might be possible to express the legality rules for type conversions more concisely. Would it be equivalent to replace the 3 listed conditions with - The operand type descended from the target type; or - The operand and target types shall potentially share descendants. ? 4) What representation attributes does an interface type have? Can values for these attributes be specified? What do T'Size and T'Alignment mean for any abstract type, not just for interface types? 5) Although an interface type which has an interface parent "is derived from" that type, it is not "a derived type". 6) Inherited homographs must be overridden. If the homographs are not mode-conformant, then this may be impossible. Life's hard. 7) Nothing here (in particular, nothing in the 8.3 stuff) is intended to change the semantics of a program which declares no interface types. 8) An example to illustrate some of the 8.3 stuff: package P1 is type T1 is interface; procedure P (X : T1) is abstract; -- P'1 end P1; package P2 is type T2 is interface with P1.T1; -- P'2 (implicit) procedure P (X : T2) is abstract; -- P'3 end P2; type D is new Some_Tagged_Type and P1.T1 and P2.T2 with null record; D inherits procedures P'4, P'5, and P'6, corresponding to P'1, P'2, and P'3, respectively. Since P'3 overides P'2, P'6 overrides P'5 by the "overriding is preserved by inheritance" rule. Since P'2 corresponds to P'1, P'5 overrides P'4 by the "inherited copies of your parent's ops hide inherited copies of your grandparent's ops" rule. 9) Yes, it really is illegal if the parent type of a "with private" type's completion happens to be descended from some interface that the private view was not descended from. This may turn out to be a pain. Relaxing the rule that a partial view must be derived from the same interfaces as the completion is derived from is not out of the question, but it could open the door for some very peculiar situations. If this example, package P is packge Pkg is type Ifc is interface; procedure Foo (X : Ifc) is abstract; end; type Parent_1 is tagged null record; type T1 is new Parent_1 with private; private type Parent_2 is new Parent_1 and Pkg.Ifc with null record; procedure Foo (X : Parent_2); -- Foo #1 type T1 is new Parent_2 with null record; end; with P; package P_Client is type T2 is new P.T1 and P.Pkg.Ifc with null record; procedure Foo (X : T2); -- Foo #2 X : T2; end P2_Client; with P_Client; package body P is ... begin Pkg.Foo (Pkg.Ifc'Class (P_Client.X)); -- call Foo #2 Pkg.Foo (Pkg.Ifc'Class (T1 (P_Client.X))); -- call Foo #1 end P2; , were legal (it is illegal because the completion of T1 is descended an interface that the partial view is not descended from), then we would have two dispatching calls to Pkg.Foo with the two controlling operands having the same tag and yet different bodies would be executed. The two conversions to Pkg.Ifc'Class would map Pkg.Foo to different slots in the same dispatch table because the source types of the conversions are different. -------- In defining interface types, a couple of design decisions seem to be of interest. First, there is the question of whether interface types should be a whole new class of type or a special kind of abstract tagged type. Since interface types have to abide by all of the restrictions of an abstract tagged type, the former approach would have involved a lot of repetition. The latter approach was chosen. Second, there is the question of derivation and operator inheritance. If a type "implements" an interface, then it must inherit operators from the interface type in order to override them. In an earlier version of this AI, the "implementing" type was not considered to be derived from the parent interface type and operator inheritance was accomplished via a separate mechanism. Now, a type which implements an interface type is defined to be derived from the interface type, thereby simplifying the operator inheritance rules and the definitions of terms such as "ancestor", "descendant", and "cover". This means that a given type may be immediately derived from more than one type, but at most one of them can be a non-interface type. It is no longer the case that every type has an "ultimate ancestor", but the term remains well-defined in all the cases where it is used in the reference manual. The rules for type conversion and membership testing need revision to cope with the possibility that an interface type and a tagged type might have a common descendant even though statically available information suggests no relationship between the two types. As an implementation model, one can think of a type which inherits operations from an interface type as defining a mapping from the primitive operations of the interface type (perhaps represented as dispatch table slot numbers) to those of the inheriting type. A dispatching call to an operation of an interface type is than accomplished by mapping the the called operation to the corresponding operation of the controlling operand's type. Alternatively, one might construct a separate dispatch table representing this alternative "view" of the "real" dispatch table, but that would require more work when deriving from a type which implements an interface. The slot-to-slot mapping remains valid and can be reused for the derived type, whereas a new alternative "view" of the dispatch table of the derived type would need to be constructed. A value of type Some_InterFace_Type'Class can then be represented at runtime as a record address paired with a reference to this dispatch table permutation map. Classwide streaming operations for interface types require some care. Consider the case of a call to Some_Interface_Type'Class'Input occuring as the controlling operand of a dispatching call. The implementation must be able to determine where to dispatch to in this case. Membership testing may also require maintaining additional information at runtime. It is intended that a type which is derived from an interface type should inherit the same subprograms as a type which is derived from both the interface type and an ancestor of the interface type. The two type definitions should be indistinguishable, in the sense that adding or deleting the explicit redundant derivation relationship should be a semantics-preserving transformation. It is intended that there should be no real difference between the two forms of derivation from interface type parents. For example, replacing the declaration type T is new Interface_Type_1 and Interface_Type_2 with null record; with type T is new Interface_Type_2 and Interface_Type_1 with null record; should be semantics-preserving. --------- An example involving interface types: type T is tagged null record; T_Var : T; package P1 is type Ifc1 is interface; procedure Op (X : Ifc1) is abstract; procedure Op1_A (X : Ifc1) is abstract; procedure Op1_B (X : Ifc1) is abstract; type Ref is access all Ifc1'Class; Procedure Foo (X : Ref); end; package P2 is type Ifc2 is interface; procedure Op (X : Ifc2) is abstract; procedure Op2_A (X : Ifc2) is abstract; procedure Op2_B (X : Ifc2) is abstract; type Ref is access all Ifc2'Class; Procedure Foo (X : Ref); end; package body P1 is ... procedure Foo (X : Ref) is type Stream_Ptr is access all Ada.Streams.Root_Stream_Type'Class; The_Stream : constant Stream_Ptr := ...; begin Op1_A (X.all); Op1_B (X.all); Ifc1'Class'Output (The_Stream, X.all); if X.all in T'Class then T_Var := T (X.all); end if; end Foo; end P1; package body P2 is ... end P2; type D is tagged with P1.Ifc1 and P2.Ifc2 and record F1, F2 : Integer; end record; procedure Op1_A (X : D); procedure Op1_B (X : D); procedure Op (X : D); procedure Op2_A (X : D); procedure Op2_B (X : D); ... X : aliased D; begin P1.Foo (X'Access); P2.Foo (X'Access); P1.Op (P1.Ifc1'Class (X)); P2.Op (P2.Ifc2'Class (X)); end; **************************************************************** From: Steve Baird Sent: Tuesday, May 13, 2003 3:59 PM AI-251 introduces null procedures. Their dynamic semantics are specified as follows: The execution of a null procedure is invoked by a subprogram call. This execution has no effect. This seems wrong. This wording should be replaced with The execution of a null procedure is invoked by a subprogram call. The execution of the subprogram_body of a null procedure has no effect. . RM 6.3.2(10) breaks the execution of a subprogram call down into several steps. Calling a null procedure is different than calling a non-null procedure only with respect to the step "The subprogram body is then executed.". The other steps (evaluation of the name or prefix of the call, parameter evaluation, and parameter copy-back) are performed in the same way as for a non-null procedure. Elaboration checking is not an issue here because a null procedure cannot have a forward declaration. **************************************************************** From: Robert I. Eachus Sent: Friday, June 13, 2003 3:31 AM There is nothing all that exciting here--I hope--but it does seem worth capturing. First, this shows one way of "fixing" the naming clash problem. And it really is a good programming in the large approach. An underlying type may be bound to a dozen or more interfaces, but changes to one binding won't affect the others, adversely or otherwise. Second, it brings up an interesting issue. In this example, List may be an interface type, but the Iterator type is in reality part of the interface. There is no problem implementing that part of the interface, but it would be nice to have a way to specify that type Iterator, and two operations on the type that don't have parameters of type List are still part of the List interface, and need to be provided by any type which implements the interface. This is actually a much more general problem. I'd say that as many as 50% of the types that would make reasonable interface types have some sort of "helper" type declarations in the package that defines the interface. Arbitrarily deciding that all the other types declared in a package that declares an interface type are part of the interface in some way might work, but then there would be the problem of binding existing types to one of these helper types. And this particular way of creating iterators shows why this can be painful. ========================================================================= Alexander Kopilovitch wrote: It seems to be an unfortunate collision that multiple new packages should be proposed when Abstract Interfaces still aren't established firmly. It is highly likely that some packages (for example, Data structures components) may look significantly better if they can use Abstract Interfaces. I personally don't see any conflict. The interface AI will allow easier bindings to C++ and Java. But in Ada, mix-ins are a better abstraction IMHO for containers. The advantage is that you can easily put objects in a container even if the original declarer of the type/class had no idea that they would be put in a container. For example: ... Yes, but what about similar containers, such as various flavors of List? And it is not an easy task to align properly the above your words with another your opinion (with which I fully agree), posted here about 3 weeks ago - I mean the following: ... In Ada you tend not to have one sort package in your toolbox, or one list type implementation, you have several. Now the programmer solving some problem sees a part of his decomposition that can be solved by a list package or a sort package, and does a generic instantiation. The problem is that there is no easy way, in Ada, to express a binding to a member of the class of sort generics, but to delay the choice of the actual mapping. This is why one of the features I expect interfaces to add to Ada is a better way of organizing collections of sort algorithms and the like. Real subtle point. Interfaces as currently planned will ALLOW multiple interface inheritance. They can also be used as I described in the first paragraph to provide multiple instances of a single abstract interface. I expect the multiple (interface) inheritance use to be common when interfacing to code written in other OO languages. The usage I described is almost not a programming convention but a way to describe the relationship between a number of otherwise unrelated abstractions. They all implement the same interface, so they can be passed to generics as instances of that interface, but otherwise there is no necessary relation between the abstractions. Can you use multiple interface inheritance to describe abstractions that match more than one interface? Sure, and it will happen. For example imagine an AVL_Tree package which clearly allows random (indexed) access, and also has an ability to do an inorder walk. It can also implement the list interface. The question is whether it will be common. The problem becomes a programming in the large issue. It is going to be much easier to create a list 'view' of the AVL_Tree package and keep it consistant with the list interface, than to maintain a package that directly implements multiple interfaces. This is probably a very good example, so let me follow it through a bit. generic type Element is private; package Lists is type List is abstract interface; function Head(L: in List) return Element; function Is_Empty(L: in List) return Boolean; procedure Append(L: in out List; E: in Element); type Iterator is private with null; function Create(L: in List) return Iterator; function Next(I: in Iterator) return Element; function At_End(I: in Iterator) return Boolean; private ... end Lists; I did the iterator this way because it brings up an interesting question about interface types. Obviously an instance of the Lists package also creates a new Iterator type, and that type is closely related to type List. But are functions like Next and At_End part of the List interface? I think it is an issue AI-251 needs to address. Now I want to create a binding between this interface and a tree type. The package spec is easy: with Lists; generic with package Trees is new AVL_Trees; -- Could make the element type explicit, but no reason to, it is -- already specifed by the element type in the AVL_Trees instance. -- (And by Elements.Element. They had better match.) package List_View is type List is private with null; function Head(L: in List) return Element; function Is_Empty(L: in List) return Boolean; procedure Append(L: in out List; E: in Element); type Iterator is private with null; function Create(L: in List) return Iterator; function Next(I: in Iterator) return Element; function At_End(I: in Iterator) return Boolean; private type List is Trees.AVL_Tree; type Iterator is Trees.Inorder_Walk with null; end List_View; Now it gets tough. Not because it is hard to write, but their is one tough decision. Head is fairly easy to write, start at the root of the tree and follow left links until you find one that is null. The contents of that node are what you want to return. Is_Empty and the Iterator operations shouldn't be too hard, since I specified that AVL_Trees provides an inorder walk interator. But what to do about Append? I see three options: 1) Always raise Program_Error. In other words, this is just a view, and not a full list implementation. 2) Raise an error if the new Element does go at the end of the list. Err, tree. This allows the Append operation to be used to insert a sorted set of Elements one at a time. 3) Insert the Element at the proper position in the tree, and invalidate any existing Iterators, that are past the position the new Element will be inserted in, either in the implementation or by fiat. (In other words, document it as an error to insert with an open Iterator.) Whichever solution you choose, all done. Notice that we have created a binding between one abstraction Lists, and another, AVL_Trees, that never explicitly uses the new interface feature to implement multiple inheritance. We could have done it, but some of the List operations only loosely make sense for an AVL_Tree viewed as a tree (the Head operation) while others will be hidden by the interface entirely. It may be that some of the operations for the List view may perfectly match the available operations for the AVL_Tree (Is_Empty?), but many will have different names. So what? The "overhead" of making the list view explicit makes maintenance much easier. ======================================================================= Added for ARG: Notice that if I make List_View.List an explicit interface derived from Lists.List or more properly from an instance of Lists.Lists, let's call in My_List, there will be a problem. I get a function: function Create(L: in List_View.List) return My_Lists.Iterator; That function is of no use to anyone. But the operations Next and At_End have no parameters (or return values) of type List. So they are not part of the (proposed Ada) interface. However, an Interface type, and Create, Next, and At_End operations are a part of the interface from a design point of view. Do I have a solution? Not yet. I can actually do what I want in current Ada--but without the multiple interface inheritance. It is trying to map an existing abstraction to a different interface that brings these issues to light. **************************************************************** From: Pascal Leroy Sent: Monday, June 16, 2003 2:10 AM > Added for ARG: Notice that if I make List_View.List an explicit > interface derived from Lists.List or more properly from an instance of > Lists.Lists, let's call in My_List, there will be a problem. I get a > function: > > function Create(L: in List_View.List) return My_Lists.Iterator; > > That function is of no use to anyone. But the operations Next and > At_End have no parameters (or return values) of type List. So they are > not part of the (proposed Ada) interface. I don't see why you claim that the above function is "of no use to anyone". It creates a perfectly good iterator. The annoyance is that, to play with this iterator, you have to use operations from both List_View and My_Lists. This happens often and I agree that it can be pretty confusing. However, I have a feeling that the Object.Operation notation would help tremendously here: Iter : My_Lists.Iterator; L : List_View.List; ... Iter := L.Create; while not Iter.At_End loop ... Iter.Next; end loop; In fact, I was not too excited about the Object.Operation notation, but I find this case quite compelling, as the programmer is relieved from having to find the "right" package. **************************************************************** From: Stephen W Baird Sent: Tuesday, September 2, 2003 4:02 PM This version of AI-251 reflects the feedback of the Toulouse meeting. Changes include: 1) Null procedures are no longer defined in this AI; they have their own AI now. 2) Subprogram overriding rules are revised. 3) Membership test legality rules are defined in terms of convertibility. 4) The term "interface ancestor" replaces the term "interface parent". 5) The discussion section and the examples are updated. [This is version /08 of the AI - ED] **************************************************************** From: Robert I Eachus Sent: Tuesday, September 2, 2003 8:45 PM Stephen W Baird wrote: > A descendant of an interface type shall be limited if and only > if the interface type is limited. I want to be sure I understand this rule and how it interacts with generic instantiation. It is currently common to define a generic with a limited type parameter, if the generic does not depend on assignment or equality for the type. I'm worried about the "only if:" part of the rule here. Can a decendant of an interface type be passed as a generic formal parameter of a limited type? The problem case of course is if the generic actually derives a type from the generic parameter: generic type Foo is limited private; .. package Bar is type Foobar is new Foo...; ... function Foob return Foobar; end Bar; package My_Bar is new Bar(Foo: Some_Non_Limited_Type_Descended_From_An_Interface); But according to your explanation: 8) Someone asked why a limited interface cannot be implemented by a non-limited type. An implementation would no longer know statically whether a dispatching function call is a call to a function with a return-by-reference result. This could be implemented, but it seems like an unnecessary complication. The language has, as a matter of general principle, steered away from constructs which would allow a limited view of an object of a non-limited tagged type. There should be no problem, because in the instance, type Foobar will be non-limited if Some_Non_Limited_Type_Descended_From_An_Interface is also non-limited. So even if an object of type Foobar is passed to some classwide operation for Some_Non_Limited_Type..., or one of its parents, there should be no problem. **************************************************************** From: Tucker Taft Sent: Sunday, September 28, 2003 9:51 AM I have had real trouble keeping the syntax for interface types in my head. Every time I write an example, I get something wrong. I just took a look at AI-251, and I can see why ;-). We aren't being consistent, and I suspect I am at least partly to blame. Here is the inconsistency I find most mystifying: type T is new Q and R and S with record ... end record; type T is new Q with R and S and private; I certainly think of "with record ...;" and "with private;" as similar constructs. I feel strongly these two need to be reconciled somehow. And here is the form for one interface inheriting from others: type P is interface with Q and R; (no "new" even though this is a "derived" interface). I am now trying to figure out what to propose for the syntax for inheriting from protected and task interfaces. Here are two possibilities: protected type T is new with ... or: protected type T with is ... and then there is the "derived" interface case: protected interface PI is new with ... or: protected interface PI with is ... Any comments/suggestions? I *think* I would prefer always putting interfaces after the word "with", or never doing so. The half-and-half approach is too confusing and hard to remember. I also feel some preference for having "new" in any type that is derived from something else. As it is now, derived interfaces don't have "new" anywhere. A radical suggestion would be to put the word "interface" out front: interface Q; interface Q is new R and S; In any case, I found the current proposal a bit of a confusing mish-mosh, and the protected/task interface proposal seems likely to make it worse... **************************************************************** From: Tucker Taft Sent: Sunday, September 28, 2003 12:04 PM > A radical suggestion would be to put the word "interface" > out front: > > interface Q; > > interface Q is new R and S; > ... This would be treating "interface" as a substitution for the term "type" instead of as a kind of type. This works better for protected and task interfaces, since the syntax clearly looks better with the word "type" *replaced* with the word "interface" rather than writing something like: protected type interface PI is ... or protected interface type PI is ... So maybe this radical suggestion has something going for it, especially if we are considering generalizing it to cover protected/task interfaces. And it allows the use of "new" for derived interfaces, which would also be more consistent in my view. **************************************************************** From: Vincent Celier Sent: Sunday, September 28, 2003 12:13 PM Ar you suggesting that these should be replaced with protected interface PI is ... ? So, protected Interface is ... would be a single_protected_declaration, and protected interface PI is ... would be an interface protected type declaration. I am not sure I would like that. **************************************************************** From: Tucker Taft Sent: Sunday, September 28, 2003 12:31 PM > Ar you suggesting that these should be replaced with > > protected interface PI is ... > > ? Yes. > > So, > > protected Interface is ... > > would be a single_protected_declaration, and > > protected interface PI is ... > > would be an interface protected type declaration. > > I am not sure I would like that. Good point. I think here we would disallow the use of "Interface" as the name of a singleton protected (or task) object, even if interface is a non-reserved keyword. That seems like an acceptable incompatibility. **************************************************************** From: Pascal Leroy Sent: Monday, September 29, 2003 2:02 AM >Here is the inconsistency I find most mystifying: > > type T is new Q and R and S with record ... end record; > > type T is new Q with R and S and private; > >I certainly think of "with record ...;" and "with private;" >as similar constructs. I feel strongly these two need >to be reconciled somehow. I agree that this needs fixing. As a matter of fact, I see this discrepancy as a bug in the AI. It appears that the syntax for private extension comes from the Vienna meeting, but at the subsequent Bedford meeting we changed the order of things, and came up with the syntax for record extension, above. Evidently the AI was not updated consistently. >And here is the form for one interface inheriting from others: > > type P is interface with Q and R; > >(no "new" even though this is a "derived" interface). Again this was discussed several times, and as a matter of fact I find it useful that there is no "new" here, as the syntax makes it clear that you are not declaring a derived type, but only composing interfaces. >I am now trying to figure out what to propose >for the syntax for inheriting from protected and >task interfaces. Here are two possibilities: > > protected type T is new with ... > or: > protected type T with is ... The first syntax is the one that is consistent with the Bedford decision. >and then there is the "derived" interface case: > > protected interface PI is new with ... > or: > protected interface PI with is ... I am concerned by the possible lookahead problems for parsers: when you reach the word "interface", you don't know if it's a keyword or an identifier. Furthermore, this syntax is inconsistent with the interface composition syntax above. >Good point. I think here we would disallow the >use of "Interface" as the name of a singleton protected >(or task) object, even if interface is a non-reserved keyword. >That seems like an acceptable incompatibility. This doesn't make sense at all. The sole purpose of unreserved keywords is to avoid incompatibilities. As you know, unreserved keywords are already a contentious issue. Unreserved keywords with incompatibilities are sure to be shot down at the WG9 level. For my part, I am going to oppose any AI that has such a blatant incompatibility anyway. **************************************************************** From: Tucker Taft Sent: Monday, September 29, 2003 5:38 AM P> I agree that this needs fixing. As a matter of fact, I see this P> discrepancy as a bug in the AI. It appears that the syntax for private P> extension comes from the Vienna meeting, but at the subsequent Bedford P> meeting we changed the order of things, and came up with the syntax for P> record extension, above. Evidently the AI was not updated consistently. OK, that makes me feel better. Though I frankly couldn't remember which we settled on. I take it that it was the first of the two above. > I am now trying to figure out what to propose > for the syntax for inheriting from protected and > task interfaces. Here are two possibilities: > > protected type T is new with ... > or: > protected type T with is ... > P> The first syntax is the one that is consistent with the Bedford decision. OK. > and then there is the "derived" interface case: > > protected interface PI is new with ... > or: > protected interface PI with is ... > P> I am concerned by the possible lookahead problems for parsers: when you P> reach the word "interface", you don't know if it's a keyword or an P> identifier. Furthermore, this syntax is inconsistent with the interface P> composition syntax above. Well, there really isn't any ambiguity, provided you are willing to look ahead a bit. I believe Vincent was more worried about conceptual ambiguity. However, I now believe that if we do decide to have non-reserved keywords, then we must not worry about conceptual ambiguity, but rather rely on programmers to have good sense. In all of our proposals it is possible to make things look funny by using the non-reserved keyword as an identifier in the same construct where the non-reserved keyword is usable as a keyword. > Good point. I think here we would disallow the > use of "Interface" as the name of a singleton protected > (or task) object, even if interface is a non-reserved keyword. > That seems like an acceptable incompatibility. > P> This doesn't make sense at all. ... I agree with you. We need to accept that the non-reserved keyword proposal is subject to the possibility of (intentional?) confusion, but so be it. As you point out, it is solely a compatibility technique, so introducing incompatibilities *and* non-reserved keywords makes little or no sense. I also realized after I replied that "protected type interface Blah is ..." has exactly the same problem. That is, "protected type Interface is ..." would be a type, whereas "protected type interface Blah is ..." would be an interface. There also would be nothing preventing someone from writing "type Interface is interface;" or, perhaps worse, "type Interface is tagged;" and other weird-o combinations. P> The sole purpose of unreserved keywords P> is to avoid incompatibilities. As you know, unreserved keywords are P> already a contentious issue. Unreserved keywords with incompatibilities P> are sure to be shot down at the WG9 level. For my part, I am going to P> oppose any AI that has such a blatant incompatibility anyway. I hear you. The proposal does *not* have these incompatibilities. They appeared only in my quick, and ill-conceived I realize now, response to Vincent. You made no comment on the idea of using "interface" as a *substitute* for the word "type" in the syntax. Any comments on that? **************************************************************** From: Robert A. Duff Sent: Monday, September 29, 2003 5:43 AM > This doesn't make sense at all. The sole purpose of unreserved keywords > is to avoid incompatibilities. As you know, unreserved keywords are > already a contentious issue. Unreserved keywords with incompatibilities > are sure to be shot down at the WG9 level. For my part, I am going to > oppose any AI that has such a blatant incompatibility anyway. The potential confusion between "Interface is" and "interface Mumble is" is no big deal, in my opinion. If you get it wrong, you get a compile-time error message, which is relatively harmless. Useful compiler options would be to warn about uses of unreserved keywords as identifiers, or to treat them as reserved words. Let's leave that sort of thing to compiler options. **************************************************************** From: Pascal Leroy Sent: Monday, September 29, 2003 7:49 AM > OK, that makes me feel better. > Though I frankly couldn't remember which we settled > on. That's why we have meeting minutes. They contain really useful information, and are make very pleasurable reading, so I think you should look at them more often. ;-) > > protected interface PI is new with ... > > Well, there really isn't any ambiguity, provided you are > willing to look ahead a bit. You're right of course, and from that perspective it is quite different from the ambiguity that Vincent pointed out (and which we must avoid at all costs). However, I am still concerned that this could wreak havoc in our LALR(1) parser. > There also would be nothing preventing someone from writing > "type Interface is interface;" or, perhaps worse, "type > Interface is tagged;" and other weird-o combinations. And we should ignore such oddities. Yes, you can write really bizarre Ada code if you try hard enough, but programmers who write code that way get what they deserve. We should be concerned about helping readability for folks who write reasonable code. > You made no comment on the idea of using "interface" as a > *substitute* for the word "type" in the syntax. Any > comments on that? I don't like the idea of using "interface" as a _substitute_ for "type", because I would like to see the word "type" whenever we declare a type. That seems like a useful readability invariant. However, if you look at the Bedford minutes, you'll see: "Pascal suggests: interface type I2 is new I1 and I2 and I0;" So I am not opposed to moving "interface" at the beginning of the declaration. **************************************************************** From: Robert Dewar Sent: Monday, September 29, 2003 8:42 AM > I am concerned by the possible lookahead problems for parsers: when you > reach the word "interface", you don't know if it's a keyword or an > identifier. Furthermore, this syntax is inconsistent with the interface > composition syntax above.=20 A little lookahead limited to a few tokens is no big deal, if necessary the lexer can do this if you are stuck with a parser with no lookahead capability. I would not distort designs too much at this level of concern. **************************************************************** From: Robert A. Duff Sent: Monday, September 29, 2003 2:40 PM > That's why we have meeting minutes. They contain really useful > information, and are make very pleasurable reading, so I think you > should look at them more often. ;-) Yes, they're at least as amusing as the comics in The Boston Globe. Perhaps I should start reading these minutes to my 10-year-old son at bed time, instead of the Isaac Asimov robot stories he demands these days. > > > protected interface PI is new with ... > > > > Well, there really isn't any ambiguity, provided you are > > willing to look ahead a bit. > > You're right of course, and from that perspective it is quite different > from the ambiguity that Vincent pointed out (and which we must avoid at > all costs). However, I am still concerned that this could wreak havoc > in our LALR(1) parser. Seems like you could make it work. I mean, at least the *lexer* can look ahead? > > There also would be nothing preventing someone from writing > > "type Interface is interface;" or, perhaps worse, "type > > Interface is tagged;" and other weird-o combinations. ..or "type Untagged_Nonlimited is tagged limited..." > And we should ignore such oddities. Yes, you can write really bizarre > Ada code if you try hard enough, but programmers who write code that way > get what they deserve. We should be concerned about helping readability > for folks who write reasonable code. I'll get up on my high horse now (basically agreeing with Pascal). My language design principle is *not* "Punish those who make bad programs." My language design principle is: "Make it easy to make good programs!" The former is futile. The latter is hard work, but it's our job. [Editor's note: Most of the following discussion is on the concept of unreserved keywords, and is filed there (in AI-284).] **************************************************************** From: Tucker Taft Sent: Sunday, October 5, 2003 12:14 PM Wording about homograph hiding for AI-251: Two homographs are not generally allowed immediately within the same declarative region unless one overrides the other (see Legality Rules below). The only declarations that are overridable are the implicit declarations for predefined operators and inherited primitive subprograms. A declaration overrides another homograph that occurs immediately within the same declarative region in the following cases: * A declaration that is not overridable overrides one that is overridable, regardless of which declaration occurs first; * The implicit declaration of an inherited operator overrides that of a predefined operator; * An implicit declaration of an inherited subprogram overrides a previous implicit declaration of an inherited subprogram. * If two or more homographs are implicitly declared at the same place: - At most one shall be a non-null non-abstract subprogram, and it overrides all the others; - If all are null procedures or abstract subprograms, then any null procedure overrides all abstract subprograms; if more than one homograph remains that is not thus overridden, then their profiles shall be fully conformant with one another, and one is chosen arbitrarily to override the others. * For an implicit declaration of a primitive subprogram in a generic unit, there is a copy of this declaration in an instance. However, a whole new set of primitive subprograms is implicitly declared for each type declared within the visible part of the instance. These new declarations occur immediately after the type declaration, and override the copied ones. The copied ones can be called only from within the instance; the new ones can be called only from outside the instance, although for tagged types, the body of a new one can be executed by a call to an old one. **************************************************************** From: Stephen W. Baird Sent: Wednesday, October 15, 2003 12:44 PM This version of AI-251 [Editor's note, this is version /09] incorporates the feedback from the Sydney ARG meeting. Significant changes include: * Tuck's suggested wording changes for 8.3 * Syntax changes - type T2 is new T1 with Ifc1 and null record; becomes type T2 is new T1 and Ifc1 with null record; - type T2 is new T1 with Ifc1 and private; becomes type T2 is new T1 and Ifc1 with private; - type IFc is interface with A and B; becomes type Ifc is interface A and B; * An observation that this AI effectively implies that tag components for all tagged types must reside at the same offset; user-specified tag placement becomes much more difficult to support. **************************************************************** From: Gary Dismukes Sent: Wednesday, October 15, 2003 3:58 PM Steve, I have a couple of comments on the 8.3 wording: > 8.3 Visibility > > Replace paragraphs 9/1 - 13 with > > ... > * If two or more homographs are implicitly declared at the same place: > > - At most one shall be a non-null non-abstract subprogram, and it > overrides all the others; Remove the hyphens for consistency with RM style (nonnull, nonabstract). > - If all are null procedures or abstract subprograms, then any null > procedure overrides all abstract subprograms; if more than one > homograph remains that is not thus overridden, then their profiles > shall be fully conformant with one another, and one is chosen > arbitrarily to override the others. Both of the above bulleted paragraphs contain legality wording (the uses of "shall"), but until now this set of paragraphs has been classified strictly as static semantics. That seems like a problematic departure from the current structure and needs to be resolved somehow. In the present version of 8.3, legality rules only appear in 8.3(26/1) (plus the Name Resolution rules in 24-25). **************************************************************** From: Stephen W. Baird Sent: Thursday, November 4, 2003 5:27 PM Gary wrote (on 10/15/03): > Both of the above bulleted paragraphs contain legality wording > (the uses of "shall"), but until now this set of paragraphs has > been classified strictly as static semantics. That seems like > a problematic departure from the current structure and needs > to be resolved somehow. In the present version of 8.3, legality > rules only appear in 8.3(26/1) (plus the Name Resolution rules > in 24-25). Good point. In response, here is another attempt at section 8.3. I moved the problematic legality rules out of the static semantics section and into the middle of 26/1, which is then split into 3 paragraphs. Gary also wrote - > Remove the hyphens for consistency with RM style You are right, but bear in mind that this is only an ASCII approximation to what will end up in the RM. [Editor's note: This wording is in version /10.] **************************************************************** From: Tucker Taft Sent: Saturday, January 10, 2004 12:50 PM The wording in AI-251 for 8.3(26/1, part 2) goes as follows: If two or more homographs are implicitly declared at the same place then at most one shall be a non-null non-abstract subprogram. If all are null or abstract, then all of the null subprograms shall be fully conformant with one another. If all are abstract, then all of the subprograms shall be fully conformant with one another. I think we need to change this to: ... declared at the same place {and not overridden by a non-overridable declaration} then at most one... And it is important that this added condition is understood to apply to the whole paragraph (which might imply some further restructuring in the wording of the paragraph). I think it is important that you can inherit from interfaces with non-fully-conforming primitives, so long as you override the offending ones. On a separate topic, I remember we debated the definition of "ultimate ancestor type." The latest AI-251 wording (2003-11-04, version 10, web-cvs rev 1.18) has ultimate ancestor not including any interface types. Is that still what we want? It seems to violate the general desire to allow an existing "normal" abstract type to be converted to an interface type without significant change to existing semantics. Perhaps we checked the RM and found it didn't matter, but I will admit it still makes me a bit uncomfortable. **************************************************************** From: Stephen W. Baird Sent: Saturday, January 10, 2004 1:58 PM Tuck says: ... > I think we need to change this to: > ... declared at the same place {and not overridden > by a non-overridable declaration} then at most one... Good point. I agree. > And it is important that this added condition is understood > to apply to the whole paragraph (which might imply some further > restructuring in the wording of the paragraph). I think your wording is fine as is. > I think it is important that you can inherit from interfaces > with non-fully-conforming primitives, so long as you override > the offending ones. Right. > On a separate topic, I remember we debated the definition > of "ultimate ancestor type." The latest AI-251 wording > (2003-11-04, version 10, web-cvs rev 1.18) has ultimate > ancestor not including any interface types. Is that > still what we want? I think that we do not want a type to have more than one ultimate ancestor. If you want to allow interface types as ultimate ancestors, that is the problem you have to deal with. > It seems to violate the general desire > to allow an existing "normal" abstract type to be converted to > an interface type without significant change to existing > semantics. Could you be more specific? > Perhaps we checked the RM and found it didn't matter, but > will admit it still makes me a bit uncomfortable. The (soon to be distributed) version of AI-251 that I'm currently working on includes the following definition of "ultimate ancestor": 3.4.1 Derivation Classes ... Replace the last sentence of paragraph 10 with: An *ultimate ancestor* of a type is an ancestor of that type that is not a descendant of any other type and that is not an interface type (see 3.9.4). Each untagged type has a unique ultimate ancestor. **************************************************************** From: Tucker Taft Sent: Saturday, January 10, 2004 10:44 PM We could define the ultimate ancestor by following the "parent" relationship, where the parent is the first type named in the derived type or interface type definition, whether or not it is an interface type. **************************************************************** From: Robert A. Duff Sent: Sunday, January 11, 2004 9:03 AM I think the notion of "ultimate ancestor" is used only for access and array types. So why should we care if tagged types can have just one, or more than one, or if the concept is even defined for them? **************************************************************** From: Pascal Leroy Sent: Monday, January 12, 2004 4:38 AM Tuck proposed: > We could define the ultimate ancestor by following the > "parent" relationship, where the parent is the first type > named in the derived type or interface type definition, whether > or not it is an interface type. I would hate to give the impression that the first type named in the derivation is special (that's in the case where it is an interface; of course it is special when it is a normal tagged type). I realize that your proposed definition would not really make the first type different from all the others, but it would be confusing. Bob commented: > I think the notion of "ultimate ancestor" is used only for > access and array types. So why should we care if tagged > types can have just one, or more than one, or if the concept > is even defined for them? Assuming that this is true (and Bob is infallible in matters of Ada doctrine) then the sensible thing to do is to define ultimate ancestor only for untagged types. **************************************************************** From: Randy Brukardt Sent: Monday, January 12, 2004 3:41 PM Pascal replied to Bob: > > > I think the notion of "ultimate ancestor" is used only for > > access and array types. So why should we care if tagged > > types can have just one, or more than one, or if the concept > > is even defined for them? > > Assuming that this is true (and Bob is infallible in matters of Ada > doctrine) then the sensible thing to do is to define ultimate ancestor > only for untagged types. "Ultimate ancestor" is used in 3.4.1(10) [definition]; 7.6.1(11) [access]; 4.5.3(6-7) [array]; 3.10.2 [access]; 4.3.2(4.a) [tagged, but not normative - it's describing what was NOT done in defining the language]; and 9.3(2) [access]. Still, I'd prefer to define it as Steve's latest description: interfaces are ignored for this purpose. One can consider the "ultimate ancestor" as the first type that can have data components and concrete primitive operations. Tucker's worry: "It seems to violate the general desire to allow an existing "normal" abstract type to be converted to an interface type without significant change to existing semantics." isn't significant here, because (1) the definition of ultimate ancestor doesn't affect the semantics of an abstract type; and (2) it isn't going to be possible to change to an interface very often anyway. Certainly, every abstract type I've ever used has had some concrete routines - they provide a convinient place to put shared operations which will rarely, if ever, need to be overridden. That's especially true if you prefer to keep your derivation trees shallow (branching directly above the root). Therefore, most of the time, you'll need to add another level to use an interface -- direct conversion is impossible. The reason to keep the semantics similar for all tagged types (including interfaces) is just to make them easier to use and understand for Ada users. But if there are necessary differences, we shouldn't get overly concerned about them. **************************************************************** From: Tucker Taft Sent: Monday, January 12, 2004 3:34 PM The alternative we discussed was to define the ultimate ancestor(s) of a type. We could then claim that for an untagged record or array type, there is exactly one, for what that's worth. It just seems odd to talk about the "ultimate ancestor" being a type that clearly has ancestors. **************************************************************** From: Randy Brukardt Sent: Tuesday, January 13, 2004 3:07 PM Bob said: > I think the notion of "ultimate ancestor" is used only for > access and array types. So why should we care if tagged > types can have just one, or more than one, or if the concept > is even defined for them? On that line, here's what my meeting notes say about this topic (I didn't remember discussing it, I just found these as I was processing the minutes...) "Ultimate ancestor" is used rarely, and only access types seem to care (and they can't have multiple ancestors.) So we should allow multiple ultimate ancestors; add a note saying that "the ultimate ancestor" is well-defined for untagged types. 3.4.1(10) would change "The ancestor" to "An ancestor". Then add "For an untagged type, there is only one ultimate ancestor, and it is designated as *the ultimate ancestor* of the type." **************************************************************** From: Robert A. Duff Sent: Tuesday, January 13, 2004 4:02 PM > Then add "For an untagged type, there is only one ultimate ancestor, and it > is designated as *the ultimate ancestor* of the type." Do we really need the ", and it is designated..." part? I mean, doesn't that follow from the normal rules of English? Just trying to cut down on useless verbiage... **************************************************************** From: Stephen W. Baird Sent: Wednesday, January 14, 2004 6:07 PM This version of AI-251 reflects the discussion of the San Diego meeting. Changes include: 1) An interface type which has an interface ancestor (e.g. "type T is interface Some_Other_Interface_Type") is a derived type. Formerly it was defined to not be a derived type and Erhard asked why. The only reason I could come up with was that it seemed odd to declare a derived type without using the keyword "new". This did not seem to justify introducing the confusing notion of a type which "is derived from" another type but which is not "a derived type". 2) Changes to the definition of "ultimate ancestor" which have already been discussed in recent ARG mail messages. 3) Tuck's recently suggested change to the overriding rules. 4) Some minor presentation changes: a definition which was formerly in a "legality rules" section was moved into a "static semantics" section "Random notes" => "Technical notes"; the addition of technical note #9 . [Editor's note: This is version /11.] ****************************************************************