CVS difference for ais/ai-00251.txt
--- ais/ai-00251.txt 2000/12/22 00:23:09 1.4
+++ ais/ai-00251.txt 2001/10/09 00:47:09 1.5
@@ -1,4 +1,4 @@
-!standard 03.04 (02) 00-12-04 AI95-00251/01
+!standard 03.04 (02) 01-10-05 AI95-00251/02
!standard 03.09.01 (02)
!class amendment 00-12-04
!status work item 00-12-04
@@ -9,20 +9,42 @@
-This proposal adds "abstract interface" types to the standard. A tagged type
-may "implement" one or more such abstract interfaces. The classwide 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.
+This proposal adds "abstract interface" types ("interface" for short) to the
+standard. A tagged type may "implement" one or more such interfaces. The
+classwide type associated with the 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
-[Problem statement here.]
+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 specs --
+the interface type has no data components and no operation implementations.
+A type may "implement" multiple interfaces, but 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. Finally,
+interfacing with Java and the new ".net" infrastructure from Microsoft would be
+significantly smoother if this concept were supported in a natural way in Ada.
-The following syntactic changes are proposed to support abstract interfaces:
+The following syntactic changes are proposed to support abstract interface types:
type_definition ::= ... | abstract_interface_definition
@@ -49,135 +71,284 @@
[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
+An abstract interface type (or "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 interface, and there is no
+record_extension_part. A formal abstract interface type is defined by a
+formal_abstract_interface_definition, or a formal_derived_type_definition with
+the word ABSTRACT but without the words "WITH PRIVATE." An abstract interface
+type may not have discriminants (though tagged types that implement it may of
+course have them).
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.
+abstract (or perhaps "is null" -- see below). 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 for 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
+A tagged type may declare that it "implements" an 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 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 interfaces. The new interface is
+also said to "implement" the other interfaces that it mentions. An interface
+"implements" itself as well. Finally, a tagged type "implements" all of its
+ancestor tagged types, including itself.
+Similarly, a private extension or formal derived type "implements" all of the
+types mentioned after the word NEW or AND.
+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.
+If the reserved word LIMITED appears in an abstract_interface_definition, the
+interface is a limited interface, and assignment and equality are not available
+on its classwide type; otherwise it is a nonlimited interface. A nonlimited
+type may implement a 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
-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
- 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.
+A formal derived type without the words "WITH PRIVATE" but with the word
+"ABSTRACT" is called a "formal derived 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.
+In a generic instantiation, for a formal derived type, the actual must
+implement all of the types that the formal implements. Furthermore, if the
+formal is not abstract, then the actual must not be abstract (and hence not an
+interface). Finally, if the formal is a formal derived interface, then the
+actual must be an abstract interface type.
+If the actual type is an interface, the formal must be an interface or a formal
+derived type with no non-interface ancestors. The formal may not be an
+abstract private tagged type, as this may create difficulties for shared
+generic implementation. This restriction does to some extent run counter to
+the goal of simplifying changing an abstract tagged type into being an abstract
+interface type during maintenance.
+In a package with a private extension declaration in its visible part, the full
+type must be a record extension that implements all of the types implemented by
+the private extension.
+A type that implements (directly or indirectly) an 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 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 interface and
+the classwide type of the interface. Converting to a covered type generally
+requires a tag check. A classwide type associated with an interface is
+considered a non-abstract, tagged, indefinite type, and matches generic formals
+in the same way as a classwide type of a "normal" tagged type.
+Membership tests are permitted where the operand's type covers the tested type.
+The 'Tag attribute is defined for abstract interface types, and uniquely
+identifies the interface from any other type or interface, except that for an
+interface declared within a task body or subprogram, the same value for 'Tag
+may be used for all elaborations of the declaration.
+In an extension aggregate, the name of the parent type may be used prior to the
+word "WITH" even if it is an interface. If it is an interface, there are
+necessarily no components to be default initialized. This facilitates moving
+from an abstract tagged type to an abstract interface type. Alternatively, a
+normal record aggregate may be used, taking advantage of the fact that the
+parent interface has no components.
+Note that only dispatching calls are permitted on the primitives of an
+interface, since they are all abstract.
+All primitives of an interface are required to be abstract to avoid any
+difficulty in resolving what happens when the "same" primitive is inherited
+from two interfaces, or from an interface and the parent type. If only the
+parent type can have a non-abstract primitive, no question arises as to which
+implementation to use. Languages that support full multiple inheritance need
+elaborate rules for resolving these kinds of conflicts.
+No data components are allowed in interfaces to avoid implementation complexity
+and inefficiency. If data components could be inherited from multiple
+interfaces, or from an interface and the parent type, 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.
+The rules are structured to ensure that an existing abstract tagged type can be
+changed to being an abstract interface type with minimal disruption to the
+existing code. The intent is that essentially anywhere an abstract tagged
+private (or null record) type is permitted, an interface is similarly
+permitted. Of course, there are additional places where only interfaces are
+allowed, but hopefully none where abstract private/null-record types are
+permitted, but interfaces are not.
+ Possible Implementation Model
+A possible implementation model for an interface "view" of an object 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 interface were declared, while its content would be determined by the
+particular type implementing the interface.
+If a given type (including potentially an interface) implements one or more
+interfaces, appropriate dispatching tables for these other interfaces should be
+efficiently accessible via the given type's dispatch table (as well as the
+dispatch table of any descendant of the type). A suggested model is to have in
+the dispatch table a pointer to an array of interfaces implemented by the type
+(where an interface is represented by a pointer to its dispatch table). This
+interface array would be organized so that a given interface is at a
+compile-time known offset in the array, analogous to how an individual dispatch
+table of primitive subprograms is laid out.
+This interface array allows an efficient view conversion from the given type to
+one of its interfaces, by fetching the desired interface (dispatch table) from
+the compile-time known slot in the interface array. Descendants of a type would
+add further interfaces to the end of this array of interfaces, so the offsets
+within the array for earlier interfaces remain the same. This model also
+applies if we define a "descendant" of an interface to be any interface or
+tagged type that names the interface immediately after the word "NEW" in its
+declaration (as opposed to naming it after an "AND").
+Converting from an interface view to a non-interface view that implements it
+can be performed using an approach commonly 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 interface view to another one that implements it will
+generally require more overhead, both for the view conversion and the
+associated run-time check. Essentially the same two approaches would be
+involved in a membership test.
+One mechanism to support interface-to-interface view conversion is to do a
+run-time search through a directory (perhaps a hash table) of all implemented
+interfaces, indexed by a unique identifier of the interface (such as the value
+returned by 'Tag), and containing a reference to the corresponding dispatch
+table for the interface. Note that this directory is *not* the same thing as
+the "interface array" mentioned above, because the unique identifier of the
+associated interface is not included in that array. Furthermore, multiple
+interfaces can share the same slot in an interface array, but each needs their
+own entry in this directory. If the target interface is found in this
+directory, then the conversion would pick up the corresponding
+interface-specific dispatch table. If the target interface is not found, then
+the conversion would raise Constraint_Error.
+If it is desired to share these directories between types that implement the
+same set of interfaces and have the same interface array layout, this directory
+could contain the slot number of the interface within the interface array,
+rather than a direct pointer to the dispatch table for the interface.
+ "Thin Pointer" Implementation Model
+It is still possible to implement references to interfaces using single-word
+pointers, but there is additional overhead when a call is to be performed.
+Essentially the same data structures suggested above can be used, but the
+directory of interfaces is searched to find the dispatch table before
+dispatching. The result of this search can be saved and reused, so that if
+there are multiple calls using the same interface reference, the directory
+search need only be performed once. In fact, one could use "fat" pointers for
+some or all parameter passing, and "thin" pointers for access-to-interfaces,
+and thereby only incur the expense when dereferencing an access value rather
+than upon individual calls. This combination of fat/thin is similar to the
+approach used in many compilers for access-to-unconstrained-arrays, where the
+access values are thin but two words are used for parameter passing.
+ Shared Generics
+For implementations that share code between generic instantiations, we need to
+ensure that the addition of interfaces to the languages does not impose
+distributed overhead on programs that make no use of interfaces. If there are
+cases where an actual type can be an interface when the formal is not, clearly
+a shared generic implementation will choose to represent the formal as a
+"normal" tagged type. In particular an access-to-formal would be "thin." What
+are the implications of this? If there is a formal access type with the formal
+as the designated type, if the actual is not also "thin" there would be
+significant overhead. This implies that either we disallow an interface
+"masquerading" as a "normal" tagged type, or use thin pointers for all
+An alternative is to use fat pointers for "normal" tagged types that could
+match the same formal as that matched by an interface. For example, in the
+rules proposed above, an interface does not match an abstract private type, but
+does match an abstract formal extension that has only interfaces specified
+after "NEW." This would mean that any normal tagged type that implements one
+or more interfaces would also have to use fat pointers, even though both words
+would point to the same dispatch table.
+ Possible "is null" rather than "is abstract" for primitives
+A possible addition to this proposal would be to allow primitive procedures of
+interfaces (and other types) to be declared as "is null" rather than "is
+abstract." The semantics of this would be that the body of the procedure is
+defined to be "is begin null; end;". This would then enable certain abstract
+types that have null (non-abstract) default implementations of certain
+primitives to also be converted to being abstract interface types. An example
+of such a type is Ada.Finalization.Controlled. [Note that there are other
+considerations which might argue for leaving Controlled as a "normal" tagged
+type, but the capability of switching other existing abstract tagged types to
+be interfaces remains potential quite useful, in our view.]
+If an interface has a null (i.e. non-abstract) primitive, then an abstract
+generic formal extension of the interface is only matched by types that have
+non-abstract implementations of this primitive. This allows non-dispatching
+calls on the primitive of the generic formal type to be made inside the
+generic, which may be necessary to enable a pass-the-buck-to-the-parent
+paradigm when overriding the operation. This is the paradigm used for
+finalization, for example, and is quite common in type hierarchies in general.
+Upon inheritance, a non-null inherited primitive overrides any null or abstract
+inherited primitives. If one or more null primitives are inherited, and no
+non-null, non-abstract primitives, then the null primitives must all be fully
+conforming (so they have the same formal parameter names and defaults), and the
+resulting inherited primitive is null. And of course, a non-dispatching call
+is permitted on a null, as opposed to abstract, primitive of an interface
+(which is really only useful in generics).
+Being able to declare "is null" in the spec of a primitive procedure has other
+advantages. For example, for code in a generic which always "passes the buck"
+to the corresponding parent primitive (as is done for Finalize, for example),
+the compiler can eliminate such a call when the parent's operation is known
+null at instantiation time. There are also obvious documentation advantages
+for someone extending a type to know that the default implementation of an
+operation is null. These advantages exist independent of this proposal for
+ type Stack is abstract; -- An abstract interface type
+ procedure Append (S : in out Stack; E : Elem) is abstract;
+ function Length (S : Stack) return Natural is abstract;
+ procedure Remove_Last (S : in out Stack; E : out Element) is abstract;
+ type Queue is abstract; -- Another interface.
+ function Length (Q : Queue) return Natural is abstract;
+ procedure Append (Q : in out Queue; E : Elem) is abstract;
+ procedure Remove_First (Q : in out Queue; E : out Element) is abstract;
+ . . .
+ type Deque is abstract new Queue and Stack;
+ -- An interface which inherits from both
+ -- the Queue and Stack interfaces.
+ procedure Prepend (Q : in out Deque; E : Elem) is abstract;
+ procedure Remove_Last (Q : in out Deque; E : out Elem) is abstract;
+ . . .
+ type My_Deque is new Deque with private;
+ -- A private extension that implements an interface
+ type My_Deque is new Blob and Deque with record
+ -- Conventional type implementing an interface.
+ . . .
+ end record;
Questions? Ask the ACAA Technical Agent