Version 1.1 of ais/ai-00251.txt

Unformatted version of ais/ai-00251.txt version 1.1
Other versions for file ais/ai-00251.txt

!standard 03.04 (02)          00-12-04 AI95-00251/01
!standard 03.09.01 (02)
!class amendment 00-12-04
!priority High
!difficulty Hard
!subject Tagged Types, Abstract Interface, Multiple Inheritance
!summary
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.
!problem
[Problem statement here.]
!proposal
The following syntactic changes are proposed 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 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 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.
!discussion
!example
!ACATS test
!appendix

!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 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.

!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 <Soeren.Henssel-Rasmussen@NOKIA.COM>
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.)

****************************************************************

Questions? Ask the ACAA Technical Agent