Version 1.6 of ais/ai-00345.txt

Unformatted version of ais/ai-00345.txt version 1.6
Other versions for file ais/ai-00345.txt

!standard 3.04.03 (00)          03-01-22 AI95-00345/03
!class amendment 03-08-07
!status work item 03-09-28
!status received 03-06-12
!priority Medium
!difficulty Hard
!subject Protected and task interfaces
!summary
protected and task interfaces are proposed. A protected or task type may specify one or more interfaces as ancestors. The synchronizing operations (entries and protected subprograms) of these interfaces are inherited by the protected or task type. if the operations are declared abstract in the interface, they must be overridden in the inheriting type.
To integrate this feature with tagged interface types, we allow task and protected types/interfaces to be derived from "normal" limited interfaces. (This added capability is not essential, and could be dropped if it overburdens the proposal.)
!problem
The object-oriented features of Ada 95 are essentially disjoint with the multi-tasking features of Ada 95. This means that it is difficult to combine synchronization with type extension and polymorphism. Although there are some approaches to doing so using access discriminants, they tend to be a bit awkward, and they don't actually prevent unsynchronized access to the object designated by the access discriminant.
!proposal
(See wording.)
!wording
NOTE: This presumes AI-251 ("normal" interface types). We will identify where we are referring to AI-251 wording.
Modify 3.4(4):
Class-wide types
Class-wide types are defined for (and belong to) each derivation class rooted at a tagged {or interface} type (see 3.9 {and 3.9.4}) ...
Modify 3.9(13):
For every subtype S of a tagged {or interface} type T (specific or class-wide), the following attribute is defined:
Replace 3.9.3(1-2) with:
An abstract type is a type intended for use as an ancestor of other types, but which is not allowed to have objects of its own.
Static semantics
Interface types (see 3.9.4) are abstract types. In addition, a tagged type that has the reserved word abstract in its declaration is an abstract type. The class-wide type (see 3.4.1) rooted at an abstract type is not itself an abstract type.
Legality Rules
Only a tagged type shall have the reserved word abstract in its declaration.
In the wording of AI-251:
Modify 3.9.4(1):
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. {A task or protected type may be derived from multiple limited interface types. Limited interface types that are specifically for use in defining task or protected types may also be defined (see 9.1 and 9.4).}
Modify 3.9.4(3):
An interface type (also called an "interface") {defined by an interface_type_definition} is a specific abstract tagged type [that is defined by an interface_type_definition].
[end of AI-251-relative modifications]
Add after 6.3.1(24):
Two subprograms or entries are type conformant (respectively mode conformant, subtype conformant, or fully conformant) if their profiles are type conformant (respectively mode conformant, subtype conformant, or fully conformant).
Two entry families are subtype conformant if their profiles are subtype conformant and their entry index subtypes match statically.
In the wording of AI-251:
Modify 8.3(9/1):
... The only declarations that are overridable are the implicit declarations for predefined operators[ and]{,} inherited primitive subprograms{, inherited abstract entries and entry families, and inherited abstract or null protected subprograms}. ...
Modify 8.3(in part of replacement for para 13):
- If all are null procedures or abstract subprograms{ (possibly protected),
entries, or entry families}, then any null procedure overrides all abstract subprograms; if more than one homograph remains that is not thus overridden, then one is chosen arbitrarily to override the others.
Modify 8.3(26/1 part 2):
If two or more homographs are implicitly declared at the same place {and there is no non-overridable declaration that overrides them} then at most one shall be a non-null non-abstract subprogram. If all are null or abstract {subprograms, entries, or entry families}, then all of the null subprograms shall be fully conformant with one another. If all are abstract, then all of the subprograms{, entries, or entry families} shall be fully conformant with one another.
[end of AI-251-relative modifications]
Add to the end of 9.1(1):
A task_interface_declaration declares a task interface type, specifying entries or entry families that shall be provided by any task type derived from the interface.
Change 9.1(2) to:
task_type_declaration ::= task type defining_identifier [known_discriminant_part] [is [new interface_list with] task_definition];
Add after 9.1(5/1):
task_interface_declaration ::= task interface defining_identifier is [new interface_list with] task_interface_definition;
task_interface_definition ::= {abstract_entry_declaration} end [task_identifier]
Change 9.1(7) to:
If a task_identifier appears at the end of a task_definition{, task_interface_definition,} or task_body, it shall repeat the defining_identifier.
Add after 9.1(8):
Each interface_subtype_mark of an interface_list appearing within a task_type_declaration or a task_interface_declaration shall denote a limited interface type -- it may be a task interface type; it shall not be a protected interface type.
If a task_type_declaration includes an interface_list, then each abstract entry or entry family inherited by the task type shall be overridden with a subtype conformant entry_declaration within the task_type_declaration. If a task_interface_declaration includes an interface_list and an abstract_entry_declaration of the task_interface_definition overrides an inherited entry or entry family, the explicitly declared entry or entry family shall be subtype conformant with the inherited entry or entry family.
Add after 9.1(9.1/1):
A task_interface_definition defines a task interface type and its first subtype. A task interface type is a limited interface type (see 3.9.4). The list of abstract_entry_declarations is the visible part of the task_interface_declaration.
If a task_type_declaration includes an interface_list, the task type is derived from each interface named in the interface_list. Similarly, if a task_interface_declaration includes an interface_list, the task interface type is derived from each interface named in the interface_list.
Add after 9.1(12/1):
The elaboration of a task_interface_declaration elaborates the task_interface_definition. The elaboration of a task_interface_definition creates the task interface type and its first subtype; it also includes the elaboration of the abstract_entry_declarations in the given order.
Add to the end of 9.4(1):
A protected_interface_declaration declares a protected interface type, specifying protected operations that are to be available for any protected type derived from the interface.
Change 9.4(2) to:
protected_type_declaration ::= protected type defining_identifier [known_discriminant_part] is [new protected_interface_name {and protected_interface_name} with] protected_definition;
Change 9.4(5/1) to:
protected_operation_declaration ::= subprogram_declaration | null_procedure_declaration | entry_declaration | aspect_clause
Add after 9.4(6):
protected_interface_declaration ::= protected interface defining_identifier is [new limited_interface_subtype_mark {and protected_interface_name} with] protected_interface_definition;
protected_interface_definition ::= {protected_interface_item} end [protected_identifier]
protected_interface_item ::= abstract_subprogram_declaration | null_procedure_declaration | abstract_entry_declaration
Change 9.4(9) to:
If a protected_identifier appears at the end of a protected_definition{, protected_interface_definition,} or protected_body, it shall repeat the defining_identifier.
Add after 9.4(10):
Each interface_subtype_mark of an interface_list appearing within a protected_type_declaration or a protected_interface_declaration shall denote a limited interface type -- it may be a protected interface type; it shall not be a task interface type.
If a protected_type_declaration includes an interface_list, then each abstract entry, abstract entry family, and abstract protected subprogram inherited by the protected type shall be overridden with a subtype conformant protected_operation_declaration within the protected_type_declaration. If a protected_interface_declaration includes an interface_list and a protected_interface_item of the protected_interface_definition overrides an inherited entry, entry family, or protected subprogram, the explicitly declared entity shall be subtype conformant with the inherited entity.
Modify 9.4(11):
A protected_interface_definition defines a protected interface type and its first subtype. A protected interface type is a limited interface type (see 3.9.4). The list of protected_interface_items is the visible part of the protected_interface_declaration.
If a protected_type_declaration includes an interface_list, the protected type is derived from each interface named in the interface_list. Similarly, if a protected_interface_declaration includes an interface_list, the protected interface type is derived from each interface named in the interface_list.
Add after 9.4(13):
The elaboration of a protected_interface_declaration elaborates the protected_interface_definition. The elaboration of a protected_interface_definition creates the protected interface type and its first subtype; it also includes the elaboration of the protected_interface_items in the given order.
Add after 9.5.2(2):
abstract_entry_declaration ::= entry defining_identifier [(discrete_subtype_definition)] parameter_profile is abstract;
Modify 9.5.2(13):
An entry_declaration in a task declaration {or an abstract_entry_declaration in a task_interface_declaration} shall not contain a specification for an access parameter (see 3.10).
Modify 9.5.2(20):
An entry_declaration {or abstract_entry_declaration} with a ...
Modify 9.5.2(22/1):
The elaboration of an entry_declaration {or abstract_entry_declaration} for an entry family ... The elaboration of an entry_declaration {or abstract_entry_declaration} for a single entry has no effect.
!example
protected interface Queue is
-- Interface for a protected queue
entry Enqueue(Elem : in Element_Type) is abstract; entry Dequeue(Elem : out Element_Type) is abstract; function Length return Natural is abstract;
end Queue;
type Queue_Ref is access all Queue'Class;
protected type Bounded_Queue(Max: Natural) is new Queue with -- Implementation of a bounded, protectected queue entry Enqueue(Elem : in Element_Type); entry Dequeue(Elem : out Element_Type); function Length return Natural; private Data: Elem_Array(1..Max); In_Index: Positive := 1; Out_Index: Positive := 1; Num_Elems: Natural := 0; end My_Queue;
task interface Worker is -- Interface for a worker task entry Queue_To_Service(Q : Queue_Ref) is abstract; end Server;
type Worker_Ref is access all Worker'Class;
task type Cyclic_Worker is new Worker with -- Implementation of a cyclic worker task entry Queue_To_Service(Q : Queue_Ref); end Cyclic_Server;
task Worker_Manager is -- Task that manages servers and queues. entry Add_Worker_Task(W : Worker_Ref); entry Add_Queue_To_Be_Serviced(Q : Queue_Ref); end Worker_Manager;
task body Worker_Manager is Worker_Array : array(1..100) of Worker_Ref; Queue_Array : array(1..10) of Queue_Ref; Num_Workers : Natural := 0; Next_Worker : Integer := Worker_Array'First; Num_Queues : Natural := 0; Next_Queue : Integer := Queue_Array'First; begin
loop
select accept Add_Worker_Task(W : Worker_Ref) do Num_Workers := Num_Workers + 1; Worker_Array(Num_Workers) := Worker_Ref(W); end Add_Worker_Task; -- Assign new task a queue to service if Num_Queues > 0 then -- Assign next queue to this worker Worker_Array(Num_Workers).Assign_Queue_To_Service( Queue_Array(Next_Queue)); -- Dynamically bound entry call
-- Advance to next queue Next_Queue := Next_Queue mod Num_Queues + 1;
end if;
or
accept Add_Queue_To_Be_Serviced(Q : Queue_Ref); Num_Queues := Num_Queues + 1; Queue_Array(Num_Queues) := Queue_Ref(Q); end Add_Queue_To_Be_Serviced;
-- Assign queue to worker if enough workers if Num_Workers >= Num_Queues then
-- This queue should be given one or more workers
declare Offset : Natural := Num_Queues-1;
begin while Offset < Num_Workers loop
-- (re) assign queue to worker Worker_Array((Next_Worker + Offset - Num_Queues)
mod Num_Workers + 1).
Assign_Queue_To_Service(Queue_Array(Num_Queues)); -- Dynamically bound entry call
Offset := Offset + Num_Queues;
end loop;
-- Advance to next worker Next_Worker := Next_Worker mod Num_Workers + 1;
end;
end if;
or
terminate;
end select;
end loop;
end Worker_Manager;
My_Queue : aliased Bounded_Queue(Max => 10); My_Server : aliased Cyclic_Server;
begin Worker_Manager.Add_Worker_Task(My_Server'access); Worker_Manager.Add_Queue_To_Be_Serviced(My_Queue'access); ...
!discussion
During the Ada 95 design process, it was recognized that type extension might be useful for protected types (and possibly task types) as well as for record types. However, at the time, both type extension and protected types were somewhat controversial, and expending energy on a combination of these two controversial features was not practical.
Since the design, however, this lack of extension of protected types has been identified as a possible target for future enhancements. In particular, a concrete proposal appeared in the May 2000 issue of ACM Transactions on Programming Languages in Systems (ACM TOPLAS), and this has formed the basis for a language amendment (AI-00250).
However, in ARG discussions, the complexity of this proposal has been of concern, and more recently a simpler suggestion was made that rather than supporting any kind of implementation inheritance, interfaces for tasks and protected types might be defined, and then concrete implementations of these interfaces could be provided. Class-wide types for these interfaces would be defined, and calls on the operations (protected subprograms and entries) defined for these interfaces could be performed given only a class-wide reference to the task or protected object.
An important advantage of eliminating inheritance of any code or data for tasks and protected types is that the "monitor"-like benefits of these constructs are preserved. All of the synchronizing operations are implemented in a single module, simplifying analysis and avoiding any inheritance "anomolies" that have been associated in the literature with combining inheritance with synchronization.
As with "normal" interfaces, a "concrete" type that inherits from an interface must override all abstract operations, but may inherit null procedures. We have not proposed null entries, since it is unclear what would be the barriers for those (True or False) or how they would fit into a task body (no accept statement?).
Objects of type <protected/task_interface>'Class can be used as the prefix in a call on a synchronizing operation, and a run-time dispatch would occur to the "appropriate" entry/protected subprogram.
Question: Should we allow declaration of operations outside the protected/task_interface definition that take directly a a protected or task interface, as opposed to the corresponding class-wide type?
Suggested answer: Initially we suggested no, but Pascal pointed out that our original proposal didn't really integrate inheritance between tagged types and task/protected types. However, we now allow "normal" limited interface types as ancestors of task and protected types or interfaces. This implies that we will have to support dispatching calls through lim-interface'class for such task and protected types, so we might as well allow task and protected interfaces to have primitive subprograms as well. Note that this capability is clearly separable from the "dispatching" inherent in invoking entries and protected subprograms, and could be dropped from the proposal if it is felt to overburden it.
--!corrigendum 03.0x.0x(0x)
!ACATS test
!appendix

From: Randy Brukardt
Sent: Thursday, June 12, 2003  7:43 PM

Tucker wrote me:

Here is an article I have submitted to
the Ada User Journal.  It might be of interest
to ARG members.  Rather than filling all of their
mailboxes with it, I thought I would just fill
yours ;-).  Once you get it posted, could you send
out an e-mail with a URL pointing to it?

---

The article is posted at
http://www.ada-auth.org/ai-files/grab_bag/oop_200y.pdf.

Happy reading!

[Editor's note: This article proposes protected interfaces.]

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

From: Robert I. Eachus
Sent: Thursday, June 12, 2003  11:19 PM

I was interested to compare the three cyclic type structure proposals as
they appear in Tuck's examples.  They all occupy about the same volume
in number of lines, but the generalized incomplete type approach
definitely looks the cleanest/most Ada-like.  There are of course,
plenty of reasons why any one of the three proposals might turn out to
be problematical for other reasons, but I think this causes me to lean a
little more in the direction I was leaning anyway.

Oh, and the write-up of protected interfaces seems to me to be the best
argument for adding interfaces to the language.  I'll have to think
about it (a lot).  I like the idea of having all queue types match a
potentially tasking safe interface.  Some implementations could be
tasking safe and others assume they are only visible in a single thread.
  A wonderful extension to Ada as an expository language for algorithm
design.  Now all we have to do is figure out how to actually implement
it. ;-)

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

From: Pascal Leroy
Sent: Friday, June 13, 2003  11:03 AM

Is this really what Tucker is proposing?  The way I read his paper,
protected and non-protected interfaces are distinct beasts and do not
mix.  So if a queue has a protected interface, all of its
implementations have to be protected.  I might be misreading the intent
of course, it's hard to know without an AI.  But because the calling
conventions of protected and non-protected operations are vastly
different, I don't see how a class-wide type could indifferently
designate a protected or a non-protected implementation.

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

From: Tucker Taft
Sent: Friday, June 13, 2003  1:22 PM

I was not proposing mixing protected interfaces
and tagged interfaces.  That would seem to be
a bad idea, given that the semantics are so different.
I'm not sure exactly what Robert Eachus had in mind,
but if you want to mix protected and tagged, you
will have to "wrap" one in the other.

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

From: Robert I. Eachus
Sent: Friday, June 13, 2003  1:22 PM

I don't think it is what Tucker is proposing, and a pragma would be
useful.   But Tucker's proposal certainly allows what I am thinking of.
  Imagine two implementations of a protected (class-wide) type. One
version, say the parent is a "normal" protected object with all of the
necessary baggage to support say a queue which is accessed by different
threads/tasks.  With Tucker's proposal you can also provide a child
implementation which will fail in one of many ways, possibly by
deadlock, if it is actually called by two different tasks.  And maybe
another child that works fine with at most two callers, but can livelock
or deadlock with three or more.

It would be nice to have a pragma that told the compiler that "this
protected body assumes a single task," if only for documentation
purposes.  Even without it, the checks/overhead in the single thread
version should be low.  With a pragma, the compiler can choose a locking
implementation which has very low overhead for passing through, and a
much higher overhead--or even Program_Error--if a caller ever blocks.
The interface is identical, and any compilers that do lock checking
outside the protected object code don't need to change.

Let me give an example.  Suppose you have a protected object type that
provides serialized access to a disk file, perhaps of a Sequential_IO
file with a (Direct_IO) associated index.  (Reading or writing would
require several calls on the protected object.) The simple
implementation would allow only one transaction in process at a time.
(Begin_Transaction, Lookup, Read or Write, Close_Transaction.)  The more
complex implementation could allow for several transactions to be in
progress at the same time.  (File locking vs. record locking.)

The key point is that this effectively adds a capability to Ada that has
been there all along, but in general becomes too difficult to use.
Right now you can customize generics, but that requires preplaning to
provide the right generic formal parameters.  A good example of this
type of customization is providing different ">" operators as parameters
to a sort routine allows the same sort routine to be used to sort on
different keys.

But allowing for full generality is just not possible.  The original
implementor of the generic has to think of all the possible
customizations ahead of time, and provide the additional generic
parameters to accomplish it.  Does this sound something like the problem
with variant records that tagged types solved?  It does to me.
Fortunately, I think that the hard work has already been done by
existing compilers, what is needed is the (trivial?) bit of effort
required to permit programmers to provide alternate bodies for generics.

I'm working on writing this up.  Basically think of it as adding derived
generics to the language.  The "new" generic has the same interface as
its parent but provides a new body.  It might be nice to allow the new
body (assuming that the generic is a package) to call operations of the
parent, but I think it is adequate to allow/require the programmer to
create an instance of the parent generic if he wants to do that.

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

From: Alan Burns
Sent: Monday, October 27, 2003  4:07 AM

The last meeting of ARG asked for some further examples
of the use of protected interfaces and task interfaces.

In general there is no current way of giving more than one
body to a PO or task. This would be useful even if no
further components are added.  Clients can link
to an interface for a Server; actual Server can be
one of many types. But also in a client server
relation, it can be useful to ensure all server tasks
provide a basic service:


task type interface Basic_Server is
  entry basic server() is abstract;
end Basic_Server;

this could lead to many server components such as:

task type interface Degraded_Server is new Basic_Server;

or

task type Fuller_Server is new Basic_Server with
  entry basic_service();
  entry full service();
end Fuller_Server;

For POs to ensure all POs have a mode change operation could be done
with a suitable interface. Also to control visability over entries:

The usual buffer:

protected type Buffer is
  entry put(X: in Element);
  entry get(X : out Element);
private
  ...
end Buffer;


Problem: Can't separate producer and consumer interface, but


protected interface Producer is
  entry Put(X: in Element) is abstract;
end producer;

protected interface Consumer is
  entry Get(X : out Element) is abstract;
end Consumer;

then

protected type Buffer is new Producer and Consumer with
  entry put(X: in Element);
  entry get(X : out Element);
private
  ...
end Buffer;

or

protected type ForwardingBuffer is new Producer and Consumer with
  entry put(X: in Element);
  entry get(X : out Element);
private
  ...
end Buffer;


A further example comes from the use of a PO as a resource controller.
Here the specification is often of the form:

protected interface Resource_Controller is
  entry Get() is abstract;
  procedure Release() is abstract;
private
  ...
end Resource_Controller;


This would allow many different implementations; also allow entries
and procedures to be added (such as Release_All).

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

From: Tucker Taft
Sent: Thursday, January 22, 2004  10:34 AM

Here is the first write-up of this AI that includes
"real" wording.  In response to Pascal's concern that
we weren't *really* integrating inheritance between
tagged and task/protected types, I have modified this
proposal to allow task/protected types to be derived
from "normal" limited interfaces as well as from
task/protected interfaces.  I don't believe this imposes
a significant added implementation burden, and clearly
provides better integration of inheritance capabilities.
However, it would be easy enough to "back out" this
added capabiltiy, if it is felt to overburden the
proposal.

In my view it would also be nice to allow non-limited
types to be derived from limited interfaces, but that
depends on fixing the return-by-reference function
morass, perhaps via the "aliased" return proposal.
In any case, that idea is essentially unrelated to
this AI, except in that it also provides further integration
of inheritance capabilities.

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

From: Tucker Taft
Sent: Thursday, January 22, 2004  10:34 AM

Here is the first write-up of this AI that includes
"real" wording.  In response to Pascal's concern that
we weren't *really* integrating inheritance between
tagged and task/protected types, I have modified this
proposal to allow task/protected types to be derived
from "normal" limited interfaces as well as from
task/protected interfaces.  I don't believe this imposes
a significant added implementation burden, and clearly
provides better integration of inheritance capabilities.
However, it would be easy enough to "back out" this
added capabiltiy, if it is felt to overburden the
proposal.

In my view it would also be nice to allow non-limited
types to be derived from limited interfaces, but that
depends on fixing the return-by-reference function
morass, perhaps via the "aliased" return proposal.
In any case, that idea is essentially unrelated to
this AI, except in that it also provides further integration
of inheritance capabilities.

[Editor's note: this is version /03.]

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

From: Robert I. Eachus
Sent: Thursday, January 22, 2004  11:35 AM

Tucker Taft wrote:

> Here is the first write-up of this AI that includes "real" wording...

I like it.  I do have one nit to pick that is not a serious issue:

>        Legality Rules
>
>    Only a tagged type shall have the reserved word abstract in its
>    declaration.

A (hostile) reading of this rule is that abstract can only appear in
(tagged) TYPE declarations.  A clearer wording might be:

"A type declaration that has the reserved word abstract in its
definition shall be a tagged type."

We could even go a little further and say: " ...a tagged view of a
type."  That makes it clear that private views must include tagged or
with, and a derived abstract type must be derived from a tagged view.

I admit I am being pedantic, but I think that is the proper way to read
proposed wording.

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

From: Robert A. Duff
Sent: Thursday, January 22, 2004  11:36 AM

> "A type declaration that has the reserved word abstract in its
> definition shall be a tagged type."

Yes, that's better wording.  Or just, "...shall be tagged".

> We could even go a little further and say: " ...a tagged view of a
> type."  That makes it clear that private views must include tagged or
> with, and a derived abstract type must be derived from a tagged view.

I don't think we need to say "view".  The RM contains many places where
"type" must be interpreted to mean "view of a type".

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

From: Tucker Taft
Sent: Thursday, January 22, 2004  11:47 AM

> Robert Eachus said:
>
> > "A type declaration that has the reserved word abstract in its
> > definition shall be a tagged type."
>
> Yes, that's better wording.  Or just, "...shall be tagged".

Well I think you need to say "shall be *for* a tagged type."
A type declaration is not itself "tagged".

In any case, I started from the admittedly somewhat awkward
existing Ada 95 wording and tried to preserve the "spirit" of it:

   Only a tagged type is allowed to be declared abstract.

became:

  Only a tagged type shall have the reserved word abstract
  in its declaration.

I don't love it, but I'm not sure I really prefer the above
proposed alternatives either.  Any other opinions?

> > We could even go a little further and say: " ...a tagged view of a
> > type."  That makes it clear that private views must include tagged or
> > with, and a derived abstract type must be derived from a tagged view.
>
> I don't think we need to say "view".  The RM contains many places where
> "type" must be interpreted to mean "view of a type".

I agree.

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

From: Robert I. Eachus
Sent: Thursday, January 22, 2004  4:01 PM

>I don't think we need to say "view".  The RM contains many places where
>"type" must be interpreted to mean "view of a type".

I have no problem with that.  And my proposed new wording was really a
fishing expedition to find a better way to say it.  One alternative is:

a) "A type declaration that contains the reserved word abstract shall
declare a tagged type."

Of course, the temptation to replace "shall" by "must" must be resisted. ;-)

b) A declaration of a non-tagged type is illegal if it contains the
reserved word abstract."

works and isn't too stilted.  Better might be:

c) "A type declaration that contains the reserved word abstract is
illegal if  it does not declare a tagged type."

But althought the meaning is clear, it is technically nonsense.  An
illegal declaration doesn't declare anything.  That brings us to:

d) "A type declaration that contains the reserved word abstract is legal
only if it declares a tagged type."

Alternative d is shorter than c, and does a good job of making its
intent clear.  Can we go further and say:

e) "An abstract type declaration shall declare a tagged type."

That opens a can of worms, but I think it is one that is worth opening.
Right now, the proposal says that task interface types and protected
interface types are also abstract types.  If we change the definitions
around, we could have "abstract types", "task interface types", and
"protected interface types" be exclusive.  I could go through and make
the other changes if people think it is worth it.  As I see it, doing
that would clean up a lot of awkward text, expecially in  the proposed
3.9.4(1), and in the changes to 9.1 and 9.4.

Abstract type does not seem to currently be heavily used in the RM, and
in any case we would now not be changing the meaning of the technical
term.  What does get used is abstract subprogram.  Adding abstract
entries means that we should add that a call to an abstract entry must
be dispatching.  (Excuse me, shall be dispatching.)

Tucker said: "In my view it would also be nice to allow non-limited
types to be derived from limited interfaces, but that depends on fixing
the return-by-reference function  morass, perhaps via the "aliased"
return proposal.
In any case, that idea is essentially unrelated to this AI, except in
that it also provides further integration
of inheritance capabilities."

If we do adopt the final alternative above, it might make it harder to
do this.  But I don't see any benefit to allowing non-tasks to inherit
from  task interfaces or non-protected types to inherit from protected
interfaces  (I also can't imagine syntax rules that would allow anything
new other than protected types inheriting from a task interface, or vice
versa.)  So it does seem to be unrelated.

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

From: Tucker Taft
Sent: Thursday, January 22, 2004   4:28 PM

> Alternative d is shorter than c, and does a good job of making its
> intent clear.  Can we go further and say:
>
> e) "An abstract type declaration shall declare a tagged type."

Now you are pretty much back to where I started with Ada 95.
The whole point was to allow abstract types to include
interfaces.  I think this is a bug in the current wording of
AI-251, by the way.

> That opens a can of worms, but I think it is one that is worth opening.
> Right now, the proposal says that task interface types and protected
> interface types are also abstract types.  If we change the definitions
> around, we could have "abstract types", "task interface types", and
> "protected interface types" be exclusive.

Why would that be a good idea?  An abstract type is one that
is not allowed to have objects (aka "instances" in OO parlance).

> ... I could go through and make
> the other changes if people think it is worth it.  As I see it, doing
> that would clean up a lot of awkward text, expecially in  the proposed
> 3.9.4(1), and in the changes to 9.1 and 9.4.

I don't agree at all.  We didn't invent the notion
of "abstract type."  It is well established in the OO
literature as a type that should not have any instances.
That applies to all interface types, as well as all
types explicitly declared "abstract."

> Abstract type does not seem to currently be heavily used in the RM, and
> in any case we would now not be changing the meaning of the technical
> term.

We would be changing the fundamental meaning of "abstract" type.

> ... What does get used is abstract subprogram.  Adding abstract
> entries means that we should add that a call to an abstract entry must
> be dispatching.  (Excuse me, shall be dispatching.)..

I'm not sure that is necessary.  Entry queues are
associated with objects, and calls are added to entry
queues and then "serviced".  I think that provides
the implicit level of indirection that is equivalent
to dispatching.  On the other hand, we may need
to say something more about protected subprogram calls
when the prefix is class-wide.

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

From: Tucker Taft
Sent: Thursday, January 22, 2004  12:02 PM

Tucker Taft wrote:

> ... I have modified this
> proposal to allow task/protected types to be derived
> from "normal" limited interfaces as well as from
> task/protected interfaces.  ...

I suppose we could take one further step, and allow
task types/interfaces to be derived from protected
interfaces that have only abstract entry/entry families.

Going the other way (deriving protected types/interfaces
from task types) doesn't seem wise, because there
are various operations you can apply to task_int'class
which wouldn't make sense for protected objects
(e.g. abort, 'identity).

On the other hand, I don't know of any operations
that can be applied to a protected object with only
entries that couldn't also be applied to a task.

Again, this is incremental functionality that is
not essential to the basic proposal, and shouldn't
be allowed to overburden it...

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

From: Randy Brukardt
Sent: Thursday, January 22, 2004   7:16 PM

Tucker Taft wrote:
>
> > ... I have modified this
> > proposal to allow task/protected types to be derived
> > from "normal" limited interfaces as well as from
> > task/protected interfaces.  ...
>
> I suppose we could take one further step, and allow
> task types/interfaces to be derived from protected
> interfaces that have only abstract entry/entry families.

I don't know if that is useful. But the problem with this proposal is that
it doesn't seem to help in all of the cases where you might want to mix
implementations.

For one thing, while a task can be derived from a "regular" limited
interface, such an interface has to be devoid of operations -- making the
capability fairly useless.

For another thing, a lot of the capabilities that you might want don't seem
to be possible.

Consider a queue interface (I'm using fixed items here for simplicity; it's
likely that this whole thing would be wrapped in a generic):

  package AQ is
    type Abstract_Queue is limited interface;
    procedure Add_Item (Queue : in out Abstract_Queue; Item : in Integer);
    procedure Remove_Item (Queue : in out Abstract_Queue; Item : out Integer);
  end AQ;

Then the standard implementation would look something like:
   with AQ;
   package NQ is
      type Queue is new AQ.Abstract_Queue with private;
      procedure Add_Item (Queue : in out Queue; Item : in Integer);
      procedure Remove_Item (Queue : in out Queue; Item : out Integer);
         -- Raises Empty_Error if empty.
      Empty_Error : exception;
   end NQ;

Of course, the interface could be used in other components that needed a
queue.

Now, say you want a version of a queue to use for multiple tasks, which
blocks on empty rather than raising an exception. You'd write something
like:

   with AQ;
   package PQ is
      protected type Blocking_Queue is new AQ.Abstract_Queue with
         procedure Add_Item (Item : in Integer);
         entry Remove_Item (Item : out Integer);
      private ...
      end Blocking_Queue;
   end PQ;

But of course that isn't allowed, because Remove_Item isn't a procedure.
Moreover, these aren't subtype conformant. Your wording fails to take into
account the implied object of the calls.
(Also, Remove_Item can't be a function, because of some stupid rule about
"in out" parameters on functions. I've made it a procedure here for that
reason - Ada functions are useless for O-O programming other than possibly
constructors. But's that's another argument. Note that the usual
work-arounds aren't going to work for a protected type, because you can't
change the implied parameter's mode or handling.)

   with AQ;
   package PQ1 is
      protected type Blocking_Queue is new AQ.Abstract_Queue with
         procedure Add_Item (Item : in Integer);
         procedure Remove_Item (Item : out Integer);
         entry Blocking_Remove_Item (Item : out Integer);
      private ...
      end Blocking_Queue;
   end PQ1;

Doesn't work either, because Remove_Item can't call Blocking_Remove_Item
(can't call blocking operations in a protected object). A Remove_Item that
raised an exception would work, but it would defeat the purpose of having
the operation.

So, you'd have to resort to a wrapper type (assuming the protected type in
PQ above exists):

   with AQ, PQ;
   package PQ2 is
      type Blocking_Queue is new AQ.Abstract_Queue with record
         Real_Queue : PQ.Blocking_Queue;
      end record;
      procedure Add_Item (Queue : in out Blocking_Queue; Item : in Integer);
      procedure Remove_Item (Queue : in out Blocking_Queue; Item : out Integer);
   end PQ2;

Now you can use your protected queue object in the other components. But
now, it is no longer protected, unless you make the component itself visible
(as I did above). So callers that want to use (say) a timed entry call would
have to call the component of the queue, not an operation on the queue.
That's pretty ugly.

Moreover, if you're willing to write wrappers, you don't need the ability to
use "regular" interfaces on protected types in the first place. Just write
the wrapper and be done with it. The beauty of this idea is to get the
compiler to write any needed wrappers. (Or generate its code so it doesn't
need any.)

Thus, I think that an entry should be able to "match" a procedure with the
proper parameter list (and no family). (This would essentially be the prefix
call mechanism in reverse.) The call would operate like a procedure call. We
already allow this in Ada, as an entry can be renamed as a procedure.
Allowing that would make the above example easy, and also would make a
"regular" interface useful for a task.

Without it, I don't see much point in the whole mechanism. Other than basic
locks (which you should avoid anyway), there don't seem to be that many
cases of wanting multiple implementations of protected objects or tasks.

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

Questions? Ask the ACAA Technical Agent