Version 1.11 of ais/ai-00251.txt
!standard 03.04 (02) 02-09-27 AI95-00251/06
!standard 03.09.01 (02)
!class amendment 00-12-04
!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. 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 interface type dispatch to code bodies associated with
specific tagged types that implement the interface.
!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 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.
!proposal
The following syntactic changes are proposed to support abstract interface types:
type_definition ::= ... | abstract_interface_definition
abstract_interface_definition ::= abstract [limited] interface
derived_type_definition ::=
[abstract] new parent_subtype_indication
[and abstract_interface_list]
[record_extension_part]
abstract_interface_list ::=
abstract_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];
NOTE: we plan to make "INTERFACE" an unreserved keyword, to minimize
upward incompatibility. The current term "reserved word" will
probably become something like "keyword", which can be either
"reserved" or "unreserved." Most new users need not be aware of
the distinction, and will probably treat all keywords as reserved.
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 "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 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 formal derived type without the words "WITH PRIVATE" but with the word
"ABSTRACT" is called a "formal derived interface".
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 private extension or private tagged type must implement all the
interfaces implemented by its full view. In other words, the full
view may not implement additional "private" interfaces. This is to
prevent the descendants of the partial view from unintentionally "stepping"
on the implementations of the primitive operations of a privately
inherited interface.
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.
"Null" Primitives
basic_declaration ::= ... | null_subprogram_declaration
null_subprogram_declaration ::= subprogram_specification is null;
As part of this proposal we include an ability to declare a
primitive procedure using "is null" rather than "is abstract."
This would be permitted for interfaces, as well as other types.
The semantics of this would be that the body of the procedure is
defined to be "is begin null; end;".
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).
Note that only dispatching calls are permitted on the non-null primitives of an
interface, since they are necessarily abstract.
!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 interfaces, or from an interface and the parent type. If only the
parent type can have a non-abstract, non-null 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.
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:
package P is
type T1 is tagged private;
...
private
type T1 is new Foo and I with ...
procedure prim_of_I(X : T1);
end P;
...
type T2 is new P1.T1 and I with ...
procedure prim_of_I(X : T2);
--
--
--
--
In the above case, the partial view of T1 would need to be:
type T1 is new I with private;
This would make it visible that I is inherited, and thereby making
the primitives of "I" as fair game for overriding outside P.
"Null" Primitives Rationale
The ability to declare a primitive of an interface as "is null"
enables existing abstract types that have null (non-abstract) default
implementations of some of their primitives to be transitioned 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.]
The ability to have non-dispatching calls on the primitive of a generic
formal interface type to be made inside the generic can 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.
For example:
type T is abstract interface;
procedure Prim1(X : T) is null;
procedure Prim2(X : T) is abstract;
generic
type NT is abstract new T with private;
--
package P is
type NN is new NT with private;
procedure Prim1(X : NN);
--
--
procedure Prim2(X : NN);
--
--
end P;
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 independently of this proposal for
interfaces.
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
access-to-interface types.
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.
!example
type Stack is abstract interface; --
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 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;
--
--
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;
--
private
type My_Deque is new Blob and Deque with record
--
. . .
end record;
!wording
[Editor's note: The discussion and proposal sections have not been updated
to match this wording.]
In 3.1 add
basic_declaration ::= ... | null_procedure_declaration
In 3.2.1 add
type_definition ::= ... | interface_type_definition
Add after 3.4(23)
A type that 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.
Add after the first sentence of 3.4.1(9):
If a type T1 implements an interface type T2 (see 3.9.4),
then T2'Class also covers all types covered by T1'Class.
In 3.9.1(2), replace
record_extension_part ::= with record_definition
with
record_extension_part ::= with [interface_list and] record_definition
In 3.9.1(3), add between sentences 3 and 4:
The accessibilty level (see 3.10.2) of a type that implements an
interface type (see 3.9.4) shall not be statically deeper than
that of the interface type.
In 3.9.1(4), add after sentence 1:
An interface type shall not be implemented in a generic body if the
interface type is declared outside that body.
Add after 3.9.3(6):
Corresponding rules apply for a type that inherits an abstract
subprogram by implementing an interface type (see 3.9.4, 3.4).
Add a new subclause after 3.9.3:
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}
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.]
Legality Rules
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 type that 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.
Static Semantics
A descendant of a type that names an interface type in an
interface_list, or that 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.
In 4.5.2(13), replace:
Two access-to-subprogram values are unequal if they designate different
subprograms. [It is unspecified whether two access
values that designate the same subprogram but are the result of distinct
evaluations of Access attribute_references are equal or unequal.]
with
Two access-to-subprogram values are unequal if they designate different
non-null subprograms. [It is unspecified whether two access
values that designate the same subprogram but are the result of distinct
evaluations of Access attribute_references are equal or unequal. It is
also unspecified whether two access values that designate different null
subprograms are equal or unequal.]
Replace 4.6(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.
In 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
Add a new subclause after 6.6:
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.
In 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;
Add after paragraph 8.3(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 that is implicitly
declared at the same place.
If one subprogram overrides another and a type inherits subprograms
corresponding to both that are implicitly declared at the same place,
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 that are implicitly
declared at the same place, then the subprogram corresponding to the
inherited subprogram overrides the other inherited subprogram.
If a type that implements an interface inherits two or more
fully conformant abstract subprograms, or two or more fully
conformant null subprograms, that are implicitly declared
at the same place, 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.
In 12.5, add
formal_type_declaration ::= ... | formal_interface_type_definition
In 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 12.5.1(18):
The actual type shall implement any interfaces that are implemented by
the formal type.
Add a new subclause after 12.5.4:
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.
Add after 13.14(7):
The implementation of an interface causes freezing of the interface type.
!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.)
****************************************************************
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;
****************************************************************
Questions? Ask the ACAA Technical Agent