Version 1.7 of ais/ai-00345.txt

Unformatted version of ais/ai-00345.txt version 1.7
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: Monday, January 12, 2004  3:51 PM

Pascal grumbled a bit about task and protected interfaces
not being unified with other kinds of interfaces.
We have also discussed the possibility of allowing
limited interfaces as ancestors of non-limited types.

It would seem possible to allow limited interfaces to be
ancestors of more than just limited tagged types and interfaces.
Perhaps we should consider (or put on the "roadmap" for Ada 2015? ;-)
allowing limited interfaces to be ancestors of non-limited interfaces/tagged
types, as well as protected/task interfaces/types.

It would mean that protected/task objects would need a "true" tag
rather than a pseudo-tag, but I doubt if that would be a huge
burden, since they often already include a pointer to some
kind of type descriptor.

Does anyone know of any specific semantic or implementation problems with
allowing limited interfaces being ancestors of non-limited types
and/or protected/task types?

We might simplify the problem by restricting it to limited interfaces
with no functions with controlling results (vaguely analogous to
the restriction we have on tagged incomplete types), to avoid
the morass associated with returning limited objects.

Note that we don't allow a tagged limited private type to be non-limited
in the full view to avoid ending up with assignment being applied to some
limited extension of the partial view that has limited components.
This isn't a problem for limited interfaces, since they don't have
any components.

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

From: Randy Brukardt
Sent: Monday, January 12, 2004  4:14 PM

> Does anyone know of any specific semantic or implementation problems with
> allowing limited interfaces being ancestors of non-limited types
> and/or protected/task types?

Steve can correct me if I'm wrong, but I believe the problem was with
return-by-reference functions. With these interfaces, you couldn't tell at
compile-time whether a function was return-by-reference or not.

Of course, if 318 is adopted with the semantics discussed at the meeting,
that would be no longer a problem. If that was in fact the only problem, we
ought to reconsider the idea *IF* (and that's a big if) we have the stomach
for the incompability in function definitions.

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

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.

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

From: Robert I. Eachus
Sent: Thursday, January 22, 2004   7:33 PM

Tucker Taft wrote:

>>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 think you are missing what I intended.  What I don't like is the term
"limited abstract type" as it is used in the current write-up: "Limited
interface types that are specifically for use in defining task or
protected types may also be defined (see 9.1 and 9.4).}"

There is nothing really wrong with the sentence.  But including the
technical/syntax terms "task interface type" and
protected interface type in "abstract types" makes for some complext
wording.  If you wanted to make the technical  term something other than
abstract type, that would be fine.  But the idea is to have a name for
those abstract types that may include task or protected components but
are not abstract task types or abstract protected types.

We got into this problem in Ada 83.  Record types with discriminants
that have default values are significantly different from other record
types.  But since Ada didn't introduce a technical term, it was one of
those things that had to be taught specially so that Ada users
understood that these types were somewhat magic.  It seems to me we are
creating a similar potential problem here.  Non-limited abstract types
are one category, abstract types that happen to be limited are another.
But task abstract types and protected abstract types are special in
other ways.  I don't like the fact that adding a well understood
qualifier ("limited") to another well understood term ("abstract type")
suddenly brings in these two categories from left field.

I think we all understand what the intent is here.  I am just trying to
look at the whole thing from outside and see how easy or hard it is to
learn or teach.  Having a separate technical term here would help.  That
is all that I am really trying to do.  I think that keeping the
technical meaning of "abstract type" unchanged, and adding "abstract
task types" and "abstract protected types" as something else works.  But
I am not tied to a particular technical term or group of technical
terms.  Just as long as specifying "abstract types that include limited
abstract types but not task abstract types or protected abstract types"
isn't such a jawbreaker.

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

I was thinking about cases where an entry call maps to an abstract entry
declaration, but there is no corresponding object.  For there to be code
like that the entry name must be bare.  (Not object.entry)  I haven't
figured out if such calls can actually get executed.

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

From: Tucker Taft
Sent: Thursday, January 22, 2004   8:15 PM

Randy Brukardt wrote:

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

Why must the interface be devoid of operations?

In the original proposal I disallowed operations
"outside" the task interface type, but I changed
that in the current proposal, and no longer disallow
primitive operations of a task/protected interface type.
These are effectively dispatching operations, just
like those of a tagged type, and would require essentially
the same mechanism to implement them.  The only difference
is that there is no hierarchy of "concrete" task/protected
types possible.  The concrete types appear at the leaves
only.  I'm not sure what difference that would make from
an implementation point of view...

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

Apparently I wasn't being clear at all.

If a protected type implements a limited interface, it must have
primitive operations *outside* the type declaration that override
those inherited from the interface.  These will presumably be
implemented in terms of protected operations of the type.  E.g.:

     package PQ is
         protected type Blocking_Queue ... [as you have it]
         procedure Add_Item (Queue : in out Blocking_Queue;
           Item : in Integer);
         procedure Remove_Item (Queue : in out Blocking_Queue;
           Item : out Integer);
     end PQ;
     package body PQ is
         protected body Blocking_Queue ... [as you would expect]
         procedure Add_Item (Queue : in out Blocking_Queue;
           Item : in Integer) is
         begin
             Queue.Add_Item(Item);
         end Add_Item;
         procedure Remove_Item (Queue : in out Blocking_Queue;
           Item : out Integer) is
         begin
             Queue.Remove_Item(Item);
         end Remove_Item;
     end PQ;

This is essentially the same thing you would have to do
if you had a generic that took a limited private type with
formal subprograms Add_Item and Remove_Item.  To pass
in a protected type to such a generic, you
basically have to create "wrappers" that turn around
and call the protected operations.  The capability
provided by interfaces is very similar to that provided
by a generic with various formal subprograms, except
of course it supports run-time polymorphism, rather than
compile-time polymorphism.  Since task and protected types
can be passed in as the actual type for a limited private
formal type, there seems some logic in allowing task
and protected types to be derived from limited interface
types.

Since protected and task types generally already have type
descriptors of some sort, giving them a structure that would
allow them to also double as (limited) tagged type descriptors
seems relatively straightforward.  Whether the effort is
worth it depends on the relative importance of this
inheritance integration.


 > ...
> Without [compiler-provided wrappers],
 > ... 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.

The advantage of the proposal seems to be that if you want a
type to be *visibly* a protected or task type, but you *also*
want it to implement some important interface, this gives you
that capability.

Alternatively, you could wrap the task or protected type
in a visible tagged record type that implemented the various interfaces,
but that seems to defeat the level of inheritance integration
we might be trying to achieve.

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

From: Randy Brukardt
Sent: Thursday, January 22, 2004   9:37 PM

> Randy Brukardt wrote:
> > 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.
>
> Why must the interface be devoid of operations?

Because the only things allowed in a task are entries, so you couldn't
provide the needed concrete operations of the type.

...
> Apparently I wasn't being clear at all.

No, I thought that you were trying to provide an integration of interfaces,
not a framework for wrappers that you could write yourself.

> If a protected type implements a limited interface, it must have
> primitive operations *outside* the type declaration that override
> those inherited from the interface.  These will presumably be
> implemented in terms of protected operations of the type.  E.g.:
>
>      package PQ is
>          protected type Blocking_Queue ... [as you have it]
>          procedure Add_Item (Queue : in out Blocking_Queue;
>            Item : in Integer);
>          procedure Remove_Item (Queue : in out Blocking_Queue;
>            Item : out Integer);
>      end PQ;
>      package body PQ is
>          protected body Blocking_Queue ... [as you would expect]
>          procedure Add_Item (Queue : in out Blocking_Queue;
>            Item : in Integer) is
>          begin
>              Queue.Add_Item(Item);
>          end Add_Item;
>          procedure Remove_Item (Queue : in out Blocking_Queue;
>            Item : out Integer) is
>          begin
>              Queue.Remove_Item(Item);
>          end Remove_Item;
>      end PQ;
>
> This is essentially the same thing you would have to do
> if you had a generic that took a limited private type with
> formal subprograms Add_Item and Remove_Item.  To pass
> in a protected type to such a generic, you
> basically have to create "wrappers" that turn around
> and call the protected operations.  The capability
> provided by interfaces is very similar to that provided
> by a generic with various formal subprograms, except
> of course it supports run-time polymorphism, rather than
> compile-time polymorphism.  Since task and protected types
> can be passed in as the actual type for a limited private
> formal type, there seems some logic in allowing task
> and protected types to be derived from limited interface
> types.
>
> Since protected and task types generally already have type
> descriptors of some sort, giving them a structure that would
> allow them to also double as (limited) tagged type descriptors
> seems relatively straightforward.  Whether the effort is
> worth it depends on the relative importance of this
> inheritance integration.

What integration? I don't see any integration here, I just see a wrapper
that you have to write virtually all of yourself. And you could have written
it yourself without any special interfaces at all (beyond the AI-251 ones).
Indeed, this wrapper saves all of two lines of code (and the need to insert
".Q" in a few places). And it's less flexible than the hand-generated
wrapper, because you can't have extra data if you need it.

So what is it about this that makes it worth any effort at all?

>
> > ...
> > Without [compiler-provided wrappers],
> > ... 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.
>
> The advantage of the proposal seems to be that if you want a
> type to be *visibly* a protected or task type, but you *also*
> want it to implement some important interface, this gives you
> that capability.
>
> Alternatively, you could wrap the task or protected type
> in a visible tagged record type that implemented the various interfaces,
> but that seems to defeat the level of inheritance integration
> we might be trying to achieve.

This doesn't come close to the level of interface integration that *I* was
expecting to get.

Ada's problem has always been that many of its features are isolated from
the others. That's especially true of the real-time ones. Here, we have a
chance to provide a real bridge, and something that saves all of two lines
of code isn't it.

If it turns out that this bridge isn't useful or workable, then we shouldn't
provide one that vaguely looks like it is simply because we can. We did that
with return-by-reference functions, and it's obvious how well *that* one
worked out.

Anyway, my abstract view of interfaces is that there basically should be
just one type. And all instances of that type ought to work the same way.
Thus, you can create a protected interface simply by adding the word
'protected' to an existing one, and no code changes will be needed (other
than to change the name). This is precisely the model that you are always
talking about to allow interoperation of abstract types and interface types.
In that case, I think it is rare that it actually will work, but in this
case, it should always work - unchanged.

In an ideal world, there would be no difference between the kinds of
interfaces at all. Limited interfaces could have entries, and they'd match
entries. (Obviously, the concrete type would have to be task or protected in
that case.) If there are no entries, any of the items could be a concrete
type (with procedures matching entries as noted above).

Note that by adding prefix calls, we allow these subprograms to be called in
either notation, so there is no notational reason for treating them
differently. A protected subprogram call could still look natural.

The compiler would generate a wrapper if needed to make the calls
consistent. This is very similar to what's done for renames and formal
subprograms (as you noted).

Of course, the world is not ideal. Entry calls cause problems, because the
implementation model for tasks and protected objects is probably very
different. Mandating that there exist some sort of call that works on both
might be a burden on implementations.

I presumed that (and *only* that) is why you retained task and protected
interfaces - so you could keep wrapperless entry calls straight. That way,
entries are only allowed in task and protected interfaces, and you know how
to call them.

Anyway, it seems to me that there is another solution to the problem of
entries. Protected types really don't have any interesting operations from
outside of themselves other than entries. That's different than tasks.
Moreover, the original problem is the extensibility of protected types. No
one was asking for extensible tasks.

So adding tasks to the equation makes it much hard to create an integrated
solution. Thus, I propose dropping task interfaces altogether.

Then, we simply allow limited interfaces to have entries. (In which case,
the concrete type must be a protected object.) The compiler will need to
make all of the subprogram calls have the right convention for the
interface, but that's not difficult (if you can rename a protected procedure
as a normal one, you can build the appropriate wrapper).

The matching rules for protected objects would be as I stated before:
subtype conformance with the profile with the PO added as the first
parameter of the profile (mode in out for procedures, mode in for
functions). And family-less entries match procedures.

That would mean that if you are programming through interfaces, you do not
even need to know if the actual implementation is a protected type or just a
tagged type. You don't have to write any wrapper routines by hand. And if
you need more powerful wrappers, you can certainly write them.

This is our last chance to integrate protected types into the rest of the
language. If we adopt separate-but-equal (which is essentially Tucker's
proposal), we're not going to have enough leaway to ever fix it. (At least
without major pain.)

Sorry about the stream-of-consciousness, it's too much to go back and
rewrite it all to match the final conclusion.

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

From: Tucker Taft
Sent: Thursday, January 22, 2004   9:51 PM

I'll have to think a while about Randy's response.
So don't expect a quick answer...

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

From: Randy Brukardt
Sent: Thursday, January 22, 2004  10:15 PM

I think I'd rather have a *good* response, rather than a *quick* one. I'm
sure there is something wrong with my idea (there always is :-), and we want
to know what it is before we adopt it...

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


Questions? Ask the ACAA Technical Agent