!standard 3.09.01 (03) 04-11-11 AI95-00345/07
!standard 2.09 (02)
!standard 3.09.03 (01)
!standard 3.09.03 (02)
!standard 3.09.04 (01)
!standard 6.03.01 (24)
!standard 9.01 (02)
!standard 9.01 (08)
!standard 9.01 (09.1/1)
!standard 9.04 (02)
!standard 9.04 (10)
!standard 9.04 (11)
!standard 9.07.02 (01)
!standard 9.07.02 (03)
!standard 9.07.02 (04)
!standard 9.07.04 (04)
!standard 9.07.04 (06)
!standard 9.08 (03)
!standard 9.09 (01)
!standard 12.05.05 (01)
!standard 12.06 (09)
!class amendment 03-08-07
!status Amendment 200Y 04-07-02
!status WG9 approved 04-11-18
!status ARG Approved 11-0-1 04-06-17
!status work item 03-09-28
!status received 03-06-12
!priority Medium
!difficulty Hard
!subject Protected and task interfaces
!summary
It is proposed that limited interfaces, as introduced by AI-251, can be
used as ancestors for protected and task types. The primitive operations
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. The overriding can be performed in
the usual way by declaring overriding primitives of the protected or
task type, or by declaring entries or protected subprograms that
override the primitive of the interface once it has been transformed
into a "prefix" notation a la AI-252.
!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
AI-251 introduces interfaces which may be composed to form further
interfaces. Tagged types can be derived from one or more interfaces
(plus possibly one other tagged type). Furthermore, interfaces may be
limited.
The proposal here is to introduce further categories of interface,
namely synchronized, protected, and task interfaces. A synchronized
interface can be implemented by either a task or protected type; a
protected interface can only be implemented by a protected type and a
task interface can only be implemented by a task type.
Moreover, an explicitly marked limited interface can be implemented by
a "normal" limited tagged type or by a protected or task type. Remember
that task and protected types are inherently limited. Note that we use
the term limited interface to refer collectively to interfaces marked
limited, synchronized, task or protected and we use explicitly limited
to refer to those actually marked as limited.
So we can write
type LI is limited interface; -- similarly type LI2
type TI is task interface;
type PI is protected interface;
type SI is synchronized interface;
and we can of course provide operations which must be abstract or null.
(Note that synchronized is a new reserved word.)
We can compose these interfaces provided that no conflict arises. The
following are all permitted:
type TI3 is task interface and LI and TI;
type LI3 is limited interface and LI and LI2;
type TI4 is task interface and LI and LI2;
type SI3 is synchronized interface and LI and LI2 and SI
The rule is simply that we can compose two or more interfaces provided
that we do not mix task and protected interfaces and the resulting
interface must be not earlier in the hierarchy, limited, synchronized,
task/protected than any of the ancestor interfaces.
We can derive a real task type or protected type from one or more of the
appropriate interfaces
task type TT is new TI with
-- and here we give entries as usual
end TT;
or
protected type PT is new LI and SI with
...
end PT;
Unlike tagged record types we cannot derive a task or protected type
from another task or protected type as well. So the derivation hierarchy
can only be one level deep once we declare actual task or protected
types.
The operations of these various interfaces are declared in the usual way
and an interface composed of several interfaces has the operations of
all of them with the same rules regarding duplication and overriding of
an abstract operation by a null one and so on as for explicitly tagged
types.
When we declare an actual task or protected type then we must implement
all of the operations of the interfaces concerned. This can be done in
two ways, either by declaring an entry or protected operation in the
specification of the task or protected object or by declaring a distinct
subprogram in the same list of declarations (but not both). Of course,
if an operation is null then it can be inherited or overridden as usual.
Thus the interface
package Pkg is
type TI is task interface;
procedure P(X: in TI) is abstract;
procedure Q(X: in TI; I: in Integer) is null;
end Pkg;
could be implemented by
package PT1 is
task type TT1 is new TI with -- P and Q implemented by entries
entry P;
entry Q(I: in Integer);
end TT1;
end PT1;
or by
package PT2 is
task type TT2 is new TI with -- P implemented by an entry
entry P; -- Q implemented by a procedure
end TT2;
procedure Q(X: in TT2; I: in Integer);
end PT2;
or even by
package PT3 is
task type TT3 is new TI with end; -- P implemented by a procedure
-- Q inherited as a null procedure
procedure P(X: in TT3; I: in Integer);
end PT3;
Note how the first parameter which denotes the task is omitted if it
is implemented by an entry. This echoes the new Obj.Op notation for
tagged types in general.
In order for the implementation of an operation by an entry of a task
type or a protected operation of a protected type to be possible some
fairly obvious conditions must be satisfied.
In all cases the first parameter of the operation must be of the task
type or protected type (it may be an access parameter).
In addition, in the case of a protected type, the first parameter of
an operation implemented by a protected procedure or entry must have
mode in or in out (and in the case of an access parameter it must be
an access to variable parameter).
If the operation does not fit these rules then it has to be implemented
as a subprogram. An important example is that a function has to be
implemented as a function in the case of a task type because there is
no such thing as a function entry.
Entries and protected operations which implement inherited operations
may be in the visible part or private part of the task or protected type
in the same way as for tagged record types.
Of course a task or protected type which implements an interface can
have additional entries and operations as well just as a derived tagged
type can have more operations than its parent.
Having declared a number of types implementing a interface then we
can dispatch to the various operations in the usual way. Thus an
interface might be implemented by a limited tagged type (plus its
various operations) and by a protected type and also by a task type.
We could then dispatch to the operations of any of these according to
the tag of the type concerned. Observe that task and protected types
are now other forms of tagged types and so we have to be careful to
say tagged record type where appropriate.
Furthermore, because a dispatching call might be to an entry or to a
procedure we now permit what appear to be procedure calls in selective
entry calls which might dispatch to an entry. We also permit calls on
entries renamed as procedures and formal subprograms since these might
also be implemented as entries.
The important point to note of course is that we can as usual assume the
common properties of the class concerned. Thus in the case of a task
interface we know that it must be implemented by a task and so the
operations such as abort and the attributes Identity, Callable and so on
can be applied. If we know that an interface is synchronized then we do
know that it has to be implemented by a task or a protected type and
so is thread-safe.
We always explicitly insert limited, synchronized, task or, protected
in the case of a limited interface in order to avoid confusion. So if
we derive a new explicitly limited interface from an exising one then we
have to write
type LI2 is limited interface and LI;
whereas in the case of normal types we write
type LT is limited....
type NLT is new LT with ... -- T is limited
then NLT is limited by the normal derivation rules. Remember by contrast
that types take their limitedness from their parent (the first one in the
list) and it is not given explicitly on type derivation.
Finally, note a rule regarding nonlimited and limited interfaces. All
descendants of a nonlimited interface have to be nonlimited because
otherwise limited types could end up with an assignment operation.
This means that we cannot write
type NI is interface; -- nonlimited
type LI is limited interface;
task type T is new NI and LI with ... -- illegal
This is illegal because the interface NI in the declaration of T is
not limited.
However, a nonlimited interface or type can be a descendant of a limited
interface. So the following are all permitted
type NI2 is interface and NI and LI; -- NI2 is nonlimited
type NT is new T and LI with ... -- T and NT are nonlimited
!wording
NOTE: This presumes AI-251 ("normal" interface types) and AI-252
("object.operation" notation). We will identify where we are referring to
AI-251 or AI-252 wording.
In 2.9(2) add the following to the list of reserved words
synchronized
Modify the first sentence of 3.9.1(3):
The parent type of a record extension shall not be a class-wide type {nor
shall it be a synchronized tagged type (see 3.9.4)}.
Replace the first sentence of 3.9.3(1) 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.
Replace 3.9.3(2) with:
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 {, task, or protected}
type may be derived from [multiple] {one or more} interface types.
Change 3.9.4(2) to:
interface_type_definition ::=
[LIMITED | TASK | PROTECTED | SYNCHRONIZED] INTERFACE [AND interface_list]
Add after 3.9.4(3):
An interface with the reserved word LIMITED, TASK, PROTECTED, or
SYNCHRONIZED in its definition is termed, respectively, a *limited
interface*, a *task interface*, a *protected interface*, or a
*synchronized interface*. In addition, all task and protected interfaces
are synchronized interfaces, and all synchronized interfaces are limited
interfaces. A view of an object that is of a task interface type (or of a
corresponding class-wide type) is a task object. Similarly, a view of an
object that is of a protected interface type (or of a corresponding
class-wide type) is a protected object.
A task or protected type derived from an interface is a tagged type. Such
a tagged type is called a *synchronized* tagged type, as are synchronized
interfaces and private extensions derived from synchronized interfaces.
Change 3.9.4(8) to:
A descendant of a nonlimited interface shall be nonlimited. A descendant
of a task interface shall be a task type or a task interface. A descendant
of a protected interface shall be a protected type or a protected
interface. A descendant of a synchronized interface shall be a task type,
a protected type, or a synchronized interface.
AARM Note:
We require that a descendant of a task, protected, or synchronized
interface repeat the explicit kind of interface it will be, rather than
simply inheriting it, so that a reader is always aware of whether the
interface provides synchronization and whether it may be implemented
only by a task or protected type. The only place where inheritance of
the kind of interface might be useful would be in a generic if you
didn't know the kind of the actual interface. However, the value of
that is low because you cannot implement an interface properly if you
don't know whether it is a task, protected, or synchronized interface.
Hence, we require the kind of the actual interface to match the kind of
the formal interface (see 12.5.5).
[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).
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(9.1/1):
If a task_type_declaration includes an interface_list, the task type
is derived from each interface named in the interface_list.
For a task_type_declaration, if the first parameter of a primitive
inherited subprogram is of the task type or an access parameter designating
the task type, and there is an entry_declaration for a single entry with
the same identifier within the task_type_declaration, having a profile that
is type conformant with that of the inherited subprogram after omitting
this first parameter, the inherited subprogram is said to be
*implemented* by the conforming task entry.
AARM Note: The inherited subprograms can only come from an interface;
a task_type_declaration inherits no subprograms of its own.
Move 9.1(8) and its heading after 9.1(9.1/1). Add the following after it:
Each interface_subtype_mark of an interface_list appearing within a
task_type_declaration shall denote a limited interface type that
is not a protected interface.
For each primitive subprogram inherited by the type declared by a
task_type_declaration, at most one of the following shall apply:
- the inherited subprogram shall be overridden with a primitive
subprogram of the task type, in which case the overriding
subprogram shall be subtype conformant with the inherited
subprogram and not abstract; or
- the inherited subprogram is implemented by a single entry of the
task type, in which case its profile after omitting the first
parameter shall be subtype conformant with that of the task
entry.
If neither applies, the inherited subprogram shall be a null procedure.
Change 9.4(2) to:
protected_type_declaration ::=
PROTECTED TYPE defining_identifier [known_discriminant_part] IS
[NEW interface_list WITH]
protected_definition;
Add the following at end of 9.4(11), then add the new paragraphs:
If a protected_type_declaration includes an interface_list, the
protected type is derived from each interface named in the
interface_list.
For a protected_type_declaration, the first parameter of a primitive
inherited subprogram is of the protected type or an access parameter
designating the protected type, and there is a
protected_operation_declaration for a protected subprogram
or single entry with the same identifier within the
protected_type_declaration, having a profile that is type conformant with
that of the inherited subprogram after omitting this first parameter, the
inherited subprogram is said to be *implemented* by the conforming
protected subprogram or entry.
AARM Note: The inherited subprograms can only come from an interface;
a protected_type_declaration inherits no subprograms of its own.
Move 9.4(10) and its heading after 9.4(11). Add the following after it:
Each interface_subtype_mark of an interface_list appearing within a
protected_type_declaration shall denote a limited interface type that
is not a task interface.
For each primitive subprogram inherited by the type declared by a
protected_type_declaration, at most one of the following shall apply:
- the inherited subprogram is overridden with a primitive
subprogram of the protected type, in which case the overriding
subprogram shall be subtype conformant with the inherited
subprogram and not abstract; or
- the inherited subprogram is implemented by a protected
subprogram or single entry of the protected type,
in which case its profile after omitting the first parameter
shall be subtype conformant with that of the protected
subprogram or entry.
If neither applies, the inherited subprogram shall be a null procedure.
If an inherited subprogram is implemented by a protected procedure or
an entry, then the first parameter of the inherited subprogram shall be
of mode OUT or IN OUT, or an access-to-variable parameter.
Add at end of 9.7.2(1):
A procedure call may appear rather than an entry call for cases
where the procedure might be implemented by an entry.
Change 9.7.2(3) to:
entry_call_alternative ::=
procedure_or_entry_call [sequence_of_statements]
Add after 9.7.2(3):
procedure_or_entry_call ::=
procedure_call_statement | entry_call_statement
Legality rules
If a procedure_call_statement is used for a procedure_or_entry_call,
the procedure_name or procedure_prefix of the procedure_call_statement
shall denote an entry renamed as a procedure, a formal subprogram, or
(a view of) a primitive subprogram of a limited interface whose first
parameter is a controlling parameter (see 3.9.2).
Static Semantics
If a procedure_call_statement is used for a procedure_or_entry_call,
and the procedure is implemented by an entry, then the procedure_name,
or the procedure_prefix and possibly the first parameter of
the procedure_call_statement, determine the target object of the call
and the entry to be called.
AARM Note:
The above says "possibly the first parameter", because Ada allows entries
to be renamed and passed as formal subprograms. In those cases, the
task or protected object is implicit in the name of the routine; otherwise
the object is an explicit parameter to the call.
Modify 9.7.2(4)
For the execution of a timed_entry_call, the entry_name{, procedure_name,
or procedure_prefix,} and {any} actual parameters are evaluated, as for a
simple entry call (see 9.5.3) {or procedure call (see 6.4)}. The expiration
time (see 9.6) for the call is determined by evaluating the
delay_expression of the delay_alternative. {If the call is an entry call or
a call on a procedure implemented by an entry,} the entry call is then
issued. {Otherwise, the call proceeds as described in 6.4 for a procedure
call, followed by the sequence_of_statements of the entry_call_alternative,
and the delay_alternative sequence_of_statements is ignored.}
Modify 9.7.4(4): change "entry_call_statement" to "procedure_or_entry_call"
Modify 9.7.4(6):
For the execution of an asynchronous_select whose triggering_statement is
[an entry_call_statement] {a procedure_or_entry_call}, the entry_name{,
procedure_name, or procedure_prefix,} and actual parameters are evaluated
as for a simple entry call (see 9.5.3) {or procedure call (see 6.4).}[,
and] {If the call is an entry call or a call on a procedure implemented by
an entry,} the entry call is issued. If the entry call is queued (or
requeued-with-abort), then the abortable_part is executed. If the entry
call is selected immediately, and never requeued-with-abort, then the
abortable_part is never started. {If the call is on a procedure that is
not implemented by an entry, the call proceeds as described in 6.4,
followed by the sequence_of_statements of the triggering_alternative, and
the abortable_part is never started.}
Modify 9.8(3):
Each task_name is expected to be of any task type {or task interface type};
they need not all be of the same [task] type.
Modify 9.9(1):
For a prefix T that is of a task type {or task interface type} (after any
implicit dereference), the following attributes are defined:
In the wording of AI-251:
Add to end of 12.5.5(5) with:
The actual type shall be a task, protected, or synchronized interface
if and only if the formal type is also, respectively, a task, protected,
or synchronized interface.
AARM Note:
We require the kind of interface type to match exactly because without
that you cannot properly implement the interface.
[end of AI-251-relative changes]
Delete the last sentence of 12.6(9) since the view can be used as an entry:
[The view is a function or procedure, never an entry.]
!example
package Example is
type Actor is task interface;
procedure Give_Name(A : Actor; Name : String) is abstract;
-- Give name to an actor
procedure Start(A : Actor) is abstract;
procedure Stop(A : Actor) is abstract;
-- Other operations
task type Stage_Actor is new Actor with
-- a task type that implements the "Actor" interface
-- all 3 primitives are implemented with entries
entry Give_Name(Name : String);
entry Start;
entry Stop;
end Stage_Actor;
type Private_Actor is new Actor with private;
-- private extension that implements Actor.
-- this is a "synchronized" tagged type so it
-- cannot be further extended (see 3.9.1(3) rule above).
private
task type Private_Actor is new Actor with
-- the full type that implements the "Actor" interface"
-- but now Give_Name is implemented with a procedure
entry Start;
entry Stop;
entry Another_Entry(X : String; Y : Integer);
end Private_Actor;
procedure Give_Name(PA : Private_Actor; Name : String);
end Example;
---------------------------------------
-- Example with queues and worker tasks
package Queues is
type Queue is synchronized interface;
-- Interface for a thread-safe queue
procedure Enqueue(Q: in out Queue; Elem : in Element_Type) is abstract;
procedure Dequeue(Q: in out Queue; Elem : out Element_Type) is abstract;
function Length(Q: Queue) return Natural is abstract;
type Queue_Ref is access all Queue'Class;
end Queues;
use Queues;
protected type Bounded_Queue(Max: Natural) is new Queues.Queue with
-- Implementation of a bounded, protected 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;
package Worker_Tasks is
type Worker is task interface;
-- Interface for a worker task
procedure Queue_To_Service(W : in out Worker; Q : Queue_Ref);
type Worker_Ref is access all Worker'Class;
end Worker_Tasks;
use Worker_Tasks;
task type Cyclic_Server is new Worker_Tasks.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) := W;
end Add_Worker_Task;
-- Assign new task a queue to service
if Num_Queues > 0 then
-- Assign next queue to this worker
Assign_Queue_To_Service(Worker_Array(Num_Workers).all,
Queue_Array(Next_Queue));
-- Dynamically bound call, implemented by entry
-- 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) := 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
Assign_Queue_To_Service(Worker_Array((Next_Worker + Offset - Num_Queues)
mod Num_Workers + 1).all,
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 "anomalies" that have been associated in the literature with
combining inheritance with synchronization.
After further investigation of interfaces specific to task and protected
types, it became apparent that the tighter integration between
inheritance and tasking features would not be accomplished unless task
and protected types could implement "normal" (limited) interfaces as
well.
The next step was simply to drop the special syntax for task and
protected interfaces completely, and define a way for entries and
protected subprograms to effectively "override" (or at least implicitly
"implement") "normal" inherited primitive subprograms. Drawing on the
"prefix" notation proposed in AI-252, we now allow a normal primitive
subprogram to be implemented by an entry or protected subprogram, so
long as it conforms to the primitive subprogram's profile after dropping
the first parameter. The first parameter must be controlling, and of
mode [IN] OUT (or access-to-variable) if implemented by a protected
procedure or entry, since protected procedures and entries are allowed
to update the protected object. Note that this requirement is not
imposed on task entries, since "constant" task objects are permitted
as the prefix object in a task entry call.
As with "normal" interfaces, a "concrete" type that inherits from an
interface must override (or "implement") all abstract operations, but
may inherit null procedures.
Having dropped special syntax for task and protected interfaces and the
associated abstract entries, we felt it was important not to lose the
ability to use selective entry calls. Hence, we now permit within a
selective entry call a dispatching call on a primitive of a limited
interface type, since it might be implemented by an entry. Having
loosened this rule, it made sense to allow the use of entries renamed
as procedures and formal subprograms in these contexts as well, since
they also might be implemented by an entry.
To allow an interface to require that it be implemented by a task or
protected type, we have brought back a bit of special syntax.
This additional syntax allows an interface to be specified as
a "task" interface or a "protected" interface. To require that
it be implemented by either a task or a protected type (to provide
properly synchronized access to the data), the syntax allows an
interface to be specified as a "synchronized" interface.
Such interfaces cannot be implemented explicitly by
"normal" tagged types.
While we're relaxing restrictions, we've also eliminated the restriction
that prevented a limited interface from being implemented by a nonlimited
type. This restriction existed primarily because of difficulties determining
the difference between normal and return-by-reference types. Since AI-318-2
has relegated return-by-reference types to the dustbin of history, there no
longer is any need for the restriction. That means that a limited interface
is nearly universal, in that it can be implemented by a tagged types (whether
limited or non-limited), a task type, or a protected type.
This proposal also makes a slight change to the syntax of interfaces, adding
the reserved word AND in order to make the definition of interfaces more
readable.
Note finally that we permit overriding in the private part. One value of
this with protected types is that this allows an interface to be implemented
without making visible whether a particular procedure of the interface is
implemented by an entry or by a protected procedure. This provides the
freedom to change the details regarding synchronization at a later date if
necessary.
IMPLEMENTATION NOTES:
Given a select statement like:
select
Sync_Obj.Add_Item(X);
Put_Line("Add_Item completed");
or
delay 5.0;
Put_Line("Add_Item timed out");
end select;
where "Sync_Obj" is a task or protected object,
an implementation might translate this into, roughly:
Status : Boolean;
Param_Block : constant Add_Item_Params := (X => X);
begin
System.RTS.Timed_Rel_{Protected,Task}_Call(
Called_Obj => Sync_Obj'Address,
Params => Param_Block'Address,
Name_Index => ,
Member_Index => 0, -- unless is member of entry family
Delay_Amount => 5.0,
Status => Status);
if Status = True then
Put_Line("Add_Item completed");
else
Put_Line("Add_Item timed out");
end if;
Timed_Rel_{Protected,Task}_Call sets Status to True if the
entry call completes, False if it times out.
To support a case where Sync_Obj is of type
Limited_Interface'Class, and Add_Item is
a primitive of Limited_Interface (called using
prefix notation), it could instead be translated
(roughly) into:
Sync_Obj : Limited_Interface'Class;
...
type Call_Status is (Not_An_Entry, Completed, Not_Completed);
Status : Call_Status := Not_An_Entry;
Param_Block : constant Add_Item_Params := (X => X);
begin
Sync_Obj._Selective_Entry_Call(
Params => Param_Block'Address,
Slot_Num => ,
Selective_Call_Info => (Kind => Timed_Rel, Delay_Amount => 5.0),
Status => Status); -- assume Status is now an in-out param
if Status /= Not_Completed then
if Call_Status = Not_An_Entry then
Add_Item(Sync_Obj, X); -- Not an entry,
-- call "normal" overriding
end if;
Put_Line("Add_Item completed");
else
Put_Line("Add_Item timed out");
end if;
Every limited interface would have to have one
"implicit" primitive, say, _Selective_Entry_Call,
which by default is null, leaving the Status as
Not_An_Entry.
However, if the interface is implemented by a
task or protected type, and one of the interface's
primitives is overridden by an entry, then
_Selective_Entry_Call would have to be overridden
with something which checked the slot number
passed in, and if it corresponded to a primitive
that was overridden by an entry, it would pass
the name/family index of that entry to the appropriate
RTS routine along with the Param_Block and the
appropriate extra selective entry information,
such as the relative delay amount. The status
of this call would then determine the status of
the call on _Selective_Entry_Call.
If the primitive did not correspond to an entry,
then the Status would be left as Not_An_Entry,
and the compiler-generated code at the call site
would then do a "normal" call on the overriding
of the primitive, which might or might not be a wrapper.
As far as distributed overhead, it would mean that
every limited interface would need an implicit
"null" procedure corresponding to _Selective_Entry_Call.
This would purely be a space overhead, since it
would never be called if the user didn't take advantage
of this ability to call limited interface primitives
in a select statement.
If they did make such a call, and it did happen to
correspond to an entry, then the overriding of
_Selective_Entry_Call could make an "efficient"
call on the appropriate RTS routine passing in
the appropriate entry name/family indices, etc.
If it didn't correspond to an entry, then
_Selective_Entry_Call would return immediately to
the compiler-generated code which would do a normal
dispatching call on the primitive.
So there wouldn't be too much overhead even when
the feature was used.
We considered supporting entry families, but ultimately
dropped the capability as not providing sufficient benefit
given the added complexity of the model.
!comment !corrigendum 2.9(02)
!comment This is now done by AI-284-2.
!comment
!comment @dinsl
!comment @b
!corrigendum 3.9.1(3)
@drepl
The parent type of a record extension shall not be a class-wide type. If the
parent type is nonlimited, then each of the components of the
@fa shall be nonlimited. The accessibility level (see
3.10.2) of a record extension shall not be statically deeper than that of its
parent type. In addition to the places where Legality Rules normally apply (see
12.3), these rules apply also in the private part of an instance of a generic
unit.
@dby
The parent type of a record extension shall not be a class-wide type nor
shall it be a synchronized tagged type (see 3.9.4). If the
parent type is nonlimited, then each of the components of the
@fa shall be nonlimited. The accessibility level (see
3.10.2) of a record extension shall not be statically deeper than that of its
parent type. In addition to the places where Legality Rules normally apply (see
12.3), these rules apply also in the private part of an instance of a generic
unit.
!corrigendum 3.9.3(1)
@drepl
An @i is a tagged type intended for use as a parent type for
type extensions, but which is not allowed to have objects of its own. An
@i is a subprogram that has no body, but is intended to be
overridden at some point when inherited. Because objects of an abstract type
cannot be created, a dispatching call to an abstract subprogram always
dispatches to some overriding body.
@dby
An @i is a type intended for use as an ancestor of other types,
but which is not allowed to have objects of its own. An @i
is a subprogram that has no body, but is intended to be overridden at some
point when inherited. Because objects of an abstract type cannot be created, a
dispatching call to an abstract subprogram always dispatches to some overriding
body.
@i>
Interface types (see 3.9.4) are abstract types. In addition, a
tagged type that has the reserved word @b 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.
!corrigendum 3.9.3(2)
@drepl
An abstract type is a specific type that has the reserved word abstract in
its declaration. Only a tagged type is allowed to be declared abstract.
@dby
Only a tagged type shall have the reserved word @b in its
declaration.
!comment The wording of AI-251 for 3.9.4 is omitted here; as a new section,
!comment we can't reference it. The changes are placed into the conflict file.
!comment We just put a dummy paragraph here:
!corrigendum 3.9.4(1)
@dinsc
An interface type is an abstract tagged type which provides a restricted
form of multiple inheritance. A tagged, task, or protected type may be
derived from one or more interface types.
!corrigendum 6.3.1(24)
@dinsa
Two @fas are @i if they are
both @fas or are both @fas, the @fas
(if any) denote the same subtype, and the corresponding @fas
of the @fas (if any) fully conform.
@dinst
Two subprograms or entries are @i (respectively @i, @i, or @i) if their profiles
are type conformant (respectively mode conformant, subtype conformant, or fully
conformant).
!corrigendum 9.1(2)
@drepl
@xcode@ft>@fa< defining_identifier [known_discriminant_part] [>@ft>@fa< task_definition];>>
@dby
@xcode@ft>@fa< defining_identifier [known_discriminant_part] [>@ft>@fa<
[>@ft>@fa< interface_list >@ft>@fa>
!corrigendum 9.1(8)
@ddel
@i>
A task declaration requires a completion, which shall be a @fa, and
every @fa shall be the completion of some task declaration.
!corrigendum 9.1(9.1/1)
@dinsa
For a task declaration without a @fa, a @fa
without @fas is assumed.
@dinss
If a @fa includes an @fa, the task type
is derived from each interface named in the @fa.
For a @fa, if the first parameter of a primitive
inherited subprogram is of the task type or an access parameter designating he
task type, and there is an @fa for a single entry with the
same identifier within the @fa, having a profile that is
type conformant with that of the inherited subprogram after omitting this first
parameter, the inherited subprogram is said to be @i by the
conforming task entry.
@i>
A task declaration requires a completion, which shall be a @fa, and
every @fa shall be the completion of some task declaration.
Each @fa of an @fa appearing within a
@fa shall denote a limited interface type that
is not a protected interface.
For each primitive subprogram inherited by the type declared by a
@fa, at most one of the following shall apply:
@xbullet
@xbullet
If neither applies, the inherited subprogram shall be a null procedure.
!corrigendum 9.4(2)
@drepl
@xcode@ft>@fa< defining_identifier [known_discriminant_part] [>@ft>@fa< protected_definition];>>
@dby
@xcode@ft>@fa< defining_identifier [known_discriminant_part] [>@ft>@fa<
[>@ft>@fa< interface_list >@ft>@fa>
!corrigendum 9.4(10)
@ddel
@i>
A protected declaration requires a completion, which shall be a
@fa, and every @fa shall be the completion of
some protected declaration.
!corrigendum 9.4(11)
@drepl
A @fa defines a protected type and its first subtype. The
list of @fas of a @fa,
together with the @fa, if any, is called the visible
part of the protected unit. The optional list of
@fas after the reserved word @b is
called the private part of the protected unit.
@dby
A @fa defines a protected type and its first subtype. The
list of @fas of a @fa,
together with the @fa, if any, is called the visible
part of the protected unit. The optional list of
@fas after the reserved word @b is
called the private part of the protected unit. If a
@fa includes an @fa, the
protected type is derived from each interface named in the @fa.
For a @fa, the first parameter of a primitive
inherited subprogram is of the protected type or an access parameter
designating the protected type, and there is a
@fa for a protected subprogram
or single entry with the same identifier within the
@fa, having a profile that is type conformant with
that of the inherited subprogram after omitting this first parameter, the
inherited subprogram is said to be @i by the conforming
protected subprogram or entry.
@i>
A protected declaration requires a completion, which shall be a
@fa, and every @fa shall be the completion of
some protected declaration.
Each @fa of an @fa appearing within a
@fa shall denote a limited interface type that
is not a task interface.
For each primitive subprogram inherited by the type declared by a
@fa, at most one of the following shall apply:
@xbullet
@xbullet
If neither applies, the inherited subprogram is a null procedure.
If an inherited subprogram is implemented by a protected procedure or
an entry, then the first parameter of the inherited subprogram shall be
of mode @b or @b, or an access-to-variable parameter.
!corrigendum 9.7.2(1)
@drepl
A @fa issues an entry call that is cancelled if the call (or
a requeue-with-abort of the call) is not selected before the expiration time
is reached.
@dby
A @fa issues an entry call that is cancelled if the call (or
a requeue-with-abort of the call) is not selected before the expiration time
is reached. A procedure call may appear rather than an entry call for cases
where the procedure might be implemented by an entry.
!corrigendum 9.7.2(3)
@drepl
@xcode>
@dby
@xcode>
@xcode>
@i>
If a @fa is used for a @fa,
the @i@fa or @i@fa of the
@fa shall denote an entry renamed as a procedure, a
formal subprogram, or (a view of) a primitive subprogram of a limited interface
whose first parameter is a controlling parameter (see 3.9.2).
@i>
If a @fa is used for a @fa,
and the procedure is implemented by an entry, then the @i@fa,
or @i@fa and possibly the first parameter of
the @fa, determine the target object of the call
and the entry to be called.
!corrigendum 9.7.2(4)
@drepl
For the execution of a @fa, the @i@fa and the
actual parameters are evaluated, as for a simple entry call (see 9.5.3). The
expiration time (see 9.6) for the call is determined by evaluating the
@i@fa of the @fa; the entry call is then
issued.
@dby
For the execution of a @fa, the @i@fa,
@i@fa, or @i@fa, and any actual
parameters are evaluated, as for a simple entry call (see 9.5.3) or procedure
call (see 6.4). The expiration time (see 9.6) for the call is determined by
evaluating the @i@fa of the @fa. If the
call is an entry call or a call on a procedure implemented by an entry, the
entry call is then issued. Otherwise, the call proceeds as described in 6.4 for
a procedure call, followed by the @fa of the
@fa, and the @fa
@fa is ignored.
!corrigendum 9.7.4(4)
@drepl
@xcode>
@dby
@xcode>
!corrigendum 9.7.4(6)
@drepl
For the execution of an @fa whose @fa
is an @fa, the @i@fa and actual parameters
are evaluated as for a simple entry call (see 9.5.3), and the entry call is
issued. If the entry call is queued (or requeued-with-abort), then the
@fa is executed. If the entry call is selected immediately,
and never requeued-with-abort, then the @fa is never started.
@dby
For the execution of an @fa whose
@fa is a @fa, the
@i@fa, @i@fa, or @i@fa,
and actual parameters are evaluated as for a simple entry
call (see 9.5.3) or procedure call (see 6.4). If the call is an entry call or a
call on a procedure implemented by an entry, the entry call is issued. If the
entry call is queued (or requeued-with-abort), then the @fa is
executed. If the entry call is selected immediately, and never
requeued-with-abort, then the @fa is never started. If the call
is on a procedure that is not implemented by an entry, the call proceeds as
described in 6.4, followed by the @fa of the
@fa, and the @fa is never started.
!corrigendum 9.8(3)
@drepl
Each @i@fa is expected to be of any task type; they need not all
be of the same task type.
@dby
Each @i@fa is expected to be of any task type or task interface
type; they need not all be of the same type.
!corrigendum 9.9(1)
@drepl
For a @fa T that is of a task type (after any implicit dereference),
the following attributes are defined:
@dby
For a @fa T that is of a task type or task interface type (after any
implicit dereference), the following attributes are defined:
!comment The wording of AI-251 for 12.5.5 is omitted here; as a new section,
!comment we can't reference it. The changes are placed into the conflict file.
!comment We just put a dummy paragraph here:
!corrigendum 12.5.5(1)
@dinsc
The class determined for a formal interface type is the class of all
interface types.
!corrigendum 12.6(9)
@drepl
A @fa declares a generic formal subprogram. The
types of the formal parameters and result, if any, of the formal subprogram are
those determined by the @fas given in the
@fa; however, independent of the particular
subtypes that are denoted by the @fas, the nominal subtypes of
the formal parameters and result, if any, are defined to be nonstatic, and
unconstrained if of an array type (no applicable index constraint is provided
in a call on a formal subprogram). In an instance, a
@fa declares a view of the actual. The profile
of this view takes its subtypes and calling convention from the original
profile of the actual entity, while taking the formal parameter @fas and
@fas from the profile given in the
@fa. The view is a function or procedure, never
an entry.
@dby
A @fa declares a generic formal subprogram. The
types of the formal parameters and result, if any, of the formal subprogram are
those determined by the @fas given in the
@fa; however, independent of the particular
subtypes that are denoted by the @fas, the nominal subtypes of
the formal parameters and result, if any, are defined to be nonstatic, and
unconstrained if of an array type (no applicable index constraint is provided
in a call on a formal subprogram). In an instance, a
@fa declares a view of the actual. The profile
of this view takes its subtypes and calling convention from the original
profile of the actual entity, while taking the formal parameter @fas and
@fas from the profile given in the
@fa.
!ACATS test
Created ACATS tests (both B and C tests) for this feature.
!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: Jean-Pierre Rosen
Sent: Friday, January 23, 2004 2:19 AM
> a) "A type declaration that contains the reserved word abstract shall
> declare a tagged type."
and later....
> d) "A type declaration that contains the reserved word abstract is legal
> only if it declares a tagged type."
I think that "shall" exactly means "is legal only if", and is both
shorter and more in line with ISO-speak.
****************************************************************
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...
****************************************************************
From: Pascal Leroy
Sent: Friday, January 23, 2004 4:04 AM
Since my name appears in this AI as a justification for its existence, I
thought I would clarify what problem I am interested in solving, and
how. In essence I concur with most of Randy's analysis, although with
some differences.
The problem that I am interested in is the situation where you have one
interface that has some implementations based on vanilla tagged types
and other implementations based on protected types (to provide for data
protection and/or synchronization). I want to be able to operate on
objects that implement this interface without having to distinguish the
tagged case from the protected case.
I am *not* interested in task types in this context. The reason is
essentially that I don't believe that the existence of a thread of
control is an "implementation detail" that can be hidden from clients:
think of termination, scheduling, etc. Furthermore, I cannot think of a
practical situation where some implementations of an abstraction would
be task-based and others would be tagged-type-based (but that may only
be a failure of my imagination). So adding task types to the mix is a
misguided attempt at orthogonality, and is likely to kill the proposal.
So now we are only dealing with tagged types and protected types. Where
I disagree with Randy is in his attempt to add entries to limited
interfaces. This is opening a big can of worms: now we'd have to deal
with primitive entries, and class-wide entries, not to mention the
confusion between dispatching calls and dispatching points :-).
Furthermore, when I write an interface, I generally have no idea what
operations might need to be synchronizing in some implementation.
We could specify that (1) a protected type can be derived from a limited
interface, and (2) primitive functions may be overridden by protected
functions, and (3) primitive procedures may be overridden by *either*
protected procedures or entries. Of course, the first parameter would
be substituted as appropriate (similarly to AI 252), and the compiler
would need to generate wrappers. Using Randy's example, this gives:
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);
Now here is a tagged implementation (just AI 251, really):
type Tagged_Queue is new Abstract_Queue with private;
procedure Add_Item (Queue : in out Tagged_Queue; Item : in Integer);
procedure Remove_Item (Queue : in out Tagged_Queue; Item : out Integer);
Here is an implementation with mere data protection:
protected type Protected_Queue is new Abstract_Queue with
procedure Add_Item (Item : in Integer);
procedure Remove_Item (Item : out Integer);
private ...
end Protected_Queue;
And here is an implementation with synchronization:
protected type Synchronizing_Queue is new Abstract_Queue with
procedure Add_Item (Item : in Integer);
entry Remove_Item (Item : out Integer);
private ...
end Synchronizing_Queue;
Note that this is a considerable simplification of the proposal, as
there is no need for protected interfaces or task interfaces (and thus
no need to argue about their syntax ;-) and tasks are unaffected by the
change. Because we don't have formal protected or task types, generics
are unaffected too. Although I obviously didn't try to write it, I
believe that the RM wording would be relatively short (which is always
good news).
(I am noticing that Tuck's AI cleverly dodges the issue of whether we
need new formal types for protected and task interfaces: this is a
lose-lose situation; if you say "no", you make them unnecessarily
different from normal interfaces; if you say "yes", boy, this gets
hard.)
****************************************************************
From: Tucker Taft
Sent: Friday, January 23, 2004 7:39 AM
> We could specify that (1) a protected type can be derived from a limited
> interface, and (2) primitive functions may be overridden by protected
> functions, and (3) primitive procedures may be overridden by *either*
> protected procedures or entries. ...
What you and Randy are proposing is certainly interesting.
With this approach, I would recommend we also allow primitive operations
to be overridden with "normal" primitive operations of the protected type,
in case a single "high-level" operation is best implemented as
multiple calls on protected operations (with intentionally preemptible
gaps). Also, there might be changes in naming required, for example,
the limited interface might have a primitive whose name is an
operator symbol. A "human"-written wrapper would be needed for
a case like that.
I also don't see any reason why a task type couldn't implement
a limited interface. Mostly all you are providing with this
proposal is compiler-generated wrappers (or am I missing something)?
You could have just as easily wrapped a task as a protected object
in a tagged record, so it seems reasonable that the compiler should
just as easily be able to generate wrappers for calling task entries
as calling protected entries.
What you are losing is the ability to use objects of type Prot_Int'Class
(or Task_Int'Class) in any constructs that require entries, i.e. various
forms of select statements. For task interfaces, you would be losing
the ability of doing an abort or retrieving the task identity.
Of course you could provide the abort/task-identity operations
via a separate limited interface which only types containing/comprising
a task would choose to implement.
The real loss seems to be select statements. Perhaps there
is some way to regain that? I can imagine various avenues:
1) Provide a standard "magic" interface that provides operations
needed to implement a select statement, and allow calls through
a class-wide object of that interface to be used in a select statement.
This sounds hard and non-obvious.
2) Allow "normal" interfaces to have abstract entries, declared *inside*
the interface definition, similar to the proposed task/protected
interface syntax. Such an interface could *only* be implemented
by a task or protected type.
3) Same as (2), but allow non-task/protected types to implement them
by overriding entries with primitive procedures of the same name
(declared "outside" the type with the "usual" AI-252 transformation).
The semantics would be that the entry barrier is assumed to be true.
4) Allow *any* primitive procedure of an interface whose first parameter
is controlling to be called in a select statement so long as "prefix"
notation is used. *If* the procedure is overridden with an entry, then
there is a barrier to worry about. Otherwise, the barrier is presumed true.
I kind of like (4). This clearly links AI-252 and AI-345 pretty closely,
and also avoids having to invent abstract entries. This also might
"nudge" us in the direction of allowing protected operations to be
called in "infix" rather than "prefix" notation (except in a
select statement!), essentially making AI-252 work "both" ways.
For upward compatibility, prefix notation would choose a protected
operation before a primitive operation, and infix notation would choose
a primitive operation before a protected operation, but if there is
no ambiguity, either kind of operation could be called either way.
I think select statements are pretty important if we are going to
satisfy the requirements of the real-time community...
****************************************************************
From: Pascal Leroy
Sent: Friday, January 23, 2004 7:59 AM
> With this approach, I would recommend we also allow primitive operations
> to be overridden with "normal" primitive operations of the
> protected type, in case a single "high-level" operation is
> best implemented as multiple calls on protected operations
> (with intentionally preemptible gaps). Also, there might be
> changes in naming required, for example, the limited
> interface might have a primitive whose name is an operator
> symbol. A "human"-written wrapper would be needed for a case
> like that.
This makes sense, but it adds complexity. I would really try to keep
the proposal dead simple if I were you. If operators are the only
problem here, we might be better off allowing them as protected
operation (I know, the syntax doesn't look too good because of the
implicit parameter).
> I also don't see any reason why a task type couldn't
> implement a limited interface. Mostly all you are providing
> with this proposal is compiler-generated wrappers (or am I
> missing something)? You could have just as easily wrapped a
> task as a protected object in a tagged record, so it seems
> reasonable that the compiler should
> just as easily be able to generate wrappers for calling task entries
> as calling protected entries.
Yes, that crossed my mind, but I guess that I see this facility as being
part of the OO side of the language, not of the real-time side, so I
cannot get excited about tasks. Note that as soon as an interface has a
primitive function, it couldn't be implemented by a task. This means
that if you want to write a fully general interface, you would have to
restrict yourselves to procedures. So we would effectively invite
people to avoid functions in interfaces, something that I don't like.
> 4) Allow *any* primitive procedure of an interface whose first parameter
> is controlling to be called in a select statement so long as "prefix"
> notation is used. *If* the procedure is overridden with an entry, then
> there is a barrier to worry about. Otherwise, the barrier is presumed true.
>
> I kind of like (4). This clearly links AI-252 and AI-345
> pretty closely, and also avoids having to invent abstract
> entries. This also might "nudge" us in the direction of
> allowing protected operations to be called in "infix" rather
> than "prefix" notation (except in a select statement!),
> essentially making AI-252 work "both" ways. For upward
> compatibility, prefix notation would choose a protected
> operation before a primitive operation, and infix notation
> would choose a primitive operation before a protected
> operation, but if there is no ambiguity, either kind of
> operation could be called either way.
>
> I think select statements are pretty important if we are
> going to satisfy the requirements of the real-time community...
Of the solutions that you propose (4) also has my preference, but I
guess I don't see select statements as such a big deal. Again, I don't
see this capability as oriented towards the real-time community. But
maybe Alan can comment on this.
****************************************************************
From: Tucker Taft
Sent: Friday, January 23, 2004 8:26 AM
Pascal Leroy wrote:
>>With this approach, I would recommend we also allow primitive operations
>>to be overridden with "normal" primitive operations of the
>>protected type, in case a single "high-level" operation is
>>best implemented as multiple calls on protected operations
>>(with intentionally preemptible gaps). Also, there might be
>>changes in naming required, for example, the limited
>>interface might have a primitive whose name is an operator
>>symbol. A "human"-written wrapper would be needed for a case
>>like that.
>
> This makes sense, but it adds complexity. I would really try to keep
> the proposal dead simple if I were you. If operators are the only
> problem here, we might be better off allowing them as protected
> operation (I know, the syntax doesn't look too good because of the
> implicit parameter).
I don't see this adding complexity. If the user writes the
wrappers themselves, it *simplifies* the job for the compiler, and
it seems non-intuitive that you can't override in the
"normal" way as well as via a protected operation. Anyone
who has used protected types with generics or as the
full type for a private type knows about wrappers. Yes
perhaps they are a bit of a pain, but they also add important
flexibility. Generally you try to have as few protected
operations as possible. It seems quite likely that they
won't necessarily map one-to-one with the operations of
an interface, and the user will want to be able to implement
some of the interface operations with a sequence of
calls on protected operations. This same kind of "adaptation"
to the needs of an interface will happen with tagged types.
Its seems even more important to allow this kind of
adaptation when implementing an interface with a protected type.
>>I also don't see any reason why a task type couldn't
>>implement a limited interface. Mostly all you are providing
>>with this proposal is compiler-generated wrappers (or am I
>>missing something)? You could have just as easily wrapped a
>>task as a protected object in a tagged record, so it seems
>>reasonable that the compiler should
>>just as easily be able to generate wrappers for calling task entries
>>as calling protected entries.
>
> Yes, that crossed my mind, but I guess that I see this facility as being
> part of the OO side of the language, not of the real-time side, so I
> cannot get excited about tasks.
We have gone out of our way to make task and protected types
have similar syntax and rules. It seems silly at this point to ignore
the similarity for lack of interest from an OO perspective.
And in OO land, active and passive objects are well understood
and both are of interest. In fact a feature of Java is that
it is easy to have an object implement the "runnable" interface.
Why shouldn't we do the same thing if it is so natural in Ada?
> ...
Note that as soon as an interface has a
> primitive function, it couldn't be implemented by a task. ...
Not if we allow primitive operations to be overridden in
the "normal" way as well. Remember that task entries
can be called on *in* parameters of a task type.
>>4) Allow *any* primitive procedure of an interface whose first parameter
>> is controlling to be called in a select statement so long as "prefix"
>> notation is used. *If* the procedure is overridden with an entry, then
>> there is a barrier to worry about. Otherwise, the barrier is presumed true.
> ...
>>I think select statements are pretty important if we are
>>going to satisfy the requirements of the real-time community...
>
> Of the solutions that you propose (4) also has my preference, but I
> guess I don't see select statements as such a big deal. Again, I don't
> see this capability as oriented towards the real-time community. But
> maybe Alan can comment on this.
The protected interface proposal was a simplification of the
protected type extension proposal which arose in the real-time
community. I don't think we should abandon their concerns
as we try to do a better job of integrating it into other
parts of the language. That sounds like burning bridges
before you come to them!
****************************************************************
From: Tucker Taft
Sent: Friday, January 23, 2004 12:21 PM
I like the idea of using limited interfaces as a way of
integrating tagged, protected, and task types. It seems
we could have two predefined interfaces, Task_Interface
and Protected_Interface. Every task type would automatically
implement Task_Interface, and every protected type would
automatically implement Protected_Interface. At a minimum
Task_Interface would have an Identity primitive function.
Given that, the routines in Ada.Task_Identification
and Ada.Task_Attributes could be used.
It might be nice to have an explicit Abort_Task procedure that worked
on Task_Interface, either as another primitive of Task_Interface,
or as a class-wide operation that took Task_Interface'Class and just
passed the result of calling Identity on the class-wide obj
to the Abort_Task procedure in Ada.Task_Identification.
It would also seem reasonable to allow a tagged type to implement
the Task_Interface, presuming it had a task component.
It could implement the Identify primitive function by returning the
Identity attribute of its task component.
It would seem if we went this route, then the unique syntax associated
with task and protected types could be masked to some degree in
generics, etc., but you could still specify that the actual
type be a task, or at least implement Task_Interface.
I also think we should endeavor to allow non-limited types
to implement limited interfaces, which would then make them
nearly universal.
****************************************************************
From: Gary Dismukes
Sent: Friday, January 23, 2004 12:33 PM
Tuck wrote:
> 4) Allow *any* primitive procedure of an interface whose first parameter
> is controlling to be called in a select statement so long as "prefix"
> notation is used. *If* the procedure is overridden with an entry, then
> there is a barrier to worry about. Otherwise, the barrier is presumed true.
>
> I kind of like (4). This clearly links AI-252 and AI-345 pretty closely,
> and also avoids having to invent abstract entries. This also might
> "nudge" us in the direction of allowing protected operations to be
> called in "infix" rather than "prefix" notation (except in a
> select statement!), essentially making AI-252 work "both" ways.
I find (4) appealing as well. Not sure what I think of allowing
infix notation for protected operations, but that seems of minor
importance.
> I think select statements are pretty important if we are going to
> satisfy the requirements of the real-time community...
Yes, that certainly seems important, especially given that the request
for this feature came largely from that community.
****************************************************************
From: Randy Brukardt
Sent: Friday, January 23, 2004 6:13 PM
...
> I don't see this adding complexity. If the user writes the
> wrappers themselves, it *simplifies* the job for the compiler, and
> it seems non-intuitive that you can't override in the
> "normal" way as well as via a protected operation. Anyone
> who has used protected types with generics or as the
> full type for a private type knows about wrappers. Yes
> perhaps they are a bit of a pain, but they also add important
> flexibility. Generally you try to have as few protected
> operations as possible. It seems quite likely that they
> won't necessarily map one-to-one with the operations of
> an interface, and the user will want to be able to implement
> some of the interface operations with a sequence of
> calls on protected operations. This same kind of "adaptation"
> to the needs of an interface will happen with tagged types.
> Its seems even more important to allow this kind of
> adaptation when implementing an interface with a protected type.
It does add complexity, because you need a tag-like outer object for
dispatching purposes, and that's something extra that the compiler would
have to generate. And, as I said yesterday, it wouldn't save much work for
the programmer - they'd end up writing nearly the same code if they
explicitly wrapped it themselves.
Moreover, if you provide the Task_Interface and Protected_Interface you
suggested earlier (and those are good ideas), and the alternative (4), then
the programmer writing such a wrapper can make it look like a task, complete
with select statements and abort. So why add the complexity to the language?
Of course, if it proves not to make much work either in wording or in
implementation, then I won't object. But I have to wonder...
...
> We have gone out of our way to make task and protected types
> have similar syntax and rules. It seems silly at this point to ignore
> the similarity for lack of interest from an OO perspective.
> And in OO land, active and passive objects are well understood
> and both are of interest. In fact a feature of Java is that
> it is easy to have an object implement the "runnable" interface.
> Why shouldn't we do the same thing if it is so natural in Ada?
Tucker is correct here in terms of value. The main reason I didn't suggest
this was implementation complexity.
> ...
> >>4) Allow *any* primitive procedure of an interface whose first parameter
> >> is controlling to be called in a select statement so long as "prefix"
> >> notation is used. *If* the procedure is overridden with an entry, then
> >> there is a barrier to worry about. Otherwise, the barrier is presumed true.
> ...
> The protected interface proposal was a simplification of the
> protected type extension proposal which arose in the real-time
> community. I don't think we should abandon their concerns
> as we try to do a better job of integrating it into other
> parts of the language. That sounds like burning bridges
> before you come to them!
Absolutely. And I agree that (4) is the cleanest from a user perspective.
The only reason I didn't suggest this was that I thought that implementation
concerns would prevent a complete unification of entries and procedures.
For instance, Janus/Ada uses different code (with different parameters) for
task entry calls and protected entry calls. In particular, how the entry is
identified is different (because there can be multiple accepts with a task,
and only one for a PT). That's OK, because it is statically known which to
generate.
This would eliminate that, and require the task supervisor to somehow be
able to determine the difference between a task and a protected object at
runtime. And we'll also lose the strong typing of the entry definition
(clearly, we can stick a small integer into an address, but we'll no longer
know which it originally was). Also, since it is the entry call itself that
implements Select statements, I don't quite see how to implement that for a
call that never goes near the task supervisor (an ordinary procedure call).
I don't think any of these is a huge deal for Janus/Ada. At worst, they'd be
a performance hit for select statements, which is unlikely to matter to our
customers. But I thought that other RTS's were much more aggressive about
using this knowledge. For instance, I thought that the idea behind ceiling
locking was to avoid all of the overhead of PO's completely, turning it all
into a normal subprogram call. If that's done, I don't see how they'll be
able to support a call that might be a task entry or might be a protected
entry or might be neither, because the details are vastly different.
The implementation I was suggesting for entries fulfilling procedures was to
wrap them to make them into regular procedures. That we know how to do
(because of renames), and there is no problem doing it. But I don't know how
to wrap an entry (of either a task or protected type) and later call it as
an entry (i.e. via a select statement). Ada 95 doesn't have access-to-entry,
after all. And I especially don't know how to put that into a tag (for one
thing, it won't fit).
So, I think we need to talk to some other implementers about this further
before spending too much time on it. If there isn't much problem, this is a
vastly preferable solution to any of the others suggested. But I'd be
surprised if the real-time folks think it is implementable.
Which it why I suggested restricting it to protected types. In that case,
select calls could be implemented however they are for protected entries,
and one probably could figure out some way to map normal subprograms to
that. (It would still be tough in our case, because the profiles wouldn't
match...but probably surmountable.) And it would be easier still with
abstract entries.
****************************************************************
From: Tucker Taft
Sent: Friday, January 23, 2004 8:38 PM
> > >Tucker Taft wrote:
> > >>With this approach, I would recommend we also allow primitive operations
> > >>to be overridden with "normal" primitive operations of the
> > >>protected type, in case a single "high-level" operation is
> > >>best implemented as multiple calls on protected operations
> > >>(with intentionally preemptible gaps). Also, there might be
> > >>changes in naming required, for example, the limited
> > >>interface might have a primitive whose name is an operator
> > >>symbol. A "human"-written wrapper would be needed for a case like that.
> ...
> It does add complexity, because you need a tag-like outer object for
> dispatching purposes, and that's something extra that the compiler would
> have to generate. And, as I said yesterday, it wouldn't save much work for
> the programmer - they'd end up writing nearly the same code if they
> explicitly wrapped it themselves.
I like your idea of having the compiler generate wrappers for
protected operations that match the interface operation.
I am simply arguing for allowing the user to write a wrapper
if necessary because the interface operation should *not*
be done as a single protected operation, but instead, requires
a break in the middle (e.g. because it needs to call a
potentially blocking operation), or because a loop is
involved which you don't want to do while holding the lock.
> Moreover, if you provide the Task_Interface and Protected_Interface you
> suggested earlier (and those are good ideas), and the alternative (4), then
> the programmer writing such a wrapper can make it look like a task, complete
> with select statements and abort. So why add the complexity to the language?
I'm not sure what complexity you are talking about now.
I am simply arguing for allowing "normal" overriding
of the interface type's primitive by one of the protected type's
primitive subprogram, as well as the "new" overriding you and
Pascal have proposed of an interface primitive by a protected type's
protected operation. I don't see how this requires a "tag"
that wouldn't have to be there anyway, since whether the
compiler or the human writes a wrapper subprogram, it needs to
end up in a dispatch table pointed to by the protected object.
> Of course, if it proves not to make much work either in wording or in
> implementation, then I won't object. But I have to wonder...
I can't imagine how it would be more work in the implementation.
And in the wording, it means simply allowing what is already
allowed, namely overriding of an operation inherited from
an ancestor. We would have to write more to disallow it,
I suspect.
> ...
> Tucker is correct here in terms of value. The main reason I didn't suggest
> this was implementation complexity.
A wrapper is a wrapper. It doesn't matter whether the wrapper
is calling a task entry or a protected entry as far as I can tell.
> ...
> Absolutely. And I agree that (4) is the cleanest from a user perspective.
> The only reason I didn't suggest this was that I thought that implementation
> concerns would prevent a complete unification of entries and procedures.
>
> For instance, Janus/Ada uses different code (with different parameters) for
> task entry calls and protected entry calls. In particular, how the entry is
> identified is different (because there can be multiple accepts with a task,
> and only one for a PT). That's OK, because it is statically known which to
> generate.
>
> This would eliminate that, and require the task supervisor to somehow be
> able to determine the difference between a task and a protected object at
> runtime. ...
I don't think this is the simplest way to support this.
I would expect that you would leave the existing code pretty
much as is, so if you know you are calling a task entry, you
would call directly to the task entry handling code, and
if you know you are calling a protected entry, you would
call directly to the protected entry, but if you don't know
whether you are actually calling an entry at all, or whether
it is a task entry or a protected entry, then you would call
a new routine, which would at run-time decide which case it
has, and *then* call the appropriate handler. This would
mean no added overhead for existing code; only code that
called "interface'Class.Operation()" in a select statement would
have to incur the overhead of deciding whether we had a task entry,
a protected entry, a protected subprogram, or a "regular"
subprogram.
> ... And we'll also lose the strong typing of the entry definition
> (clearly, we can stick a small integer into an address, but we'll no longer
> know which it originally was).
I'm not sure what you mean by "strong" typing. I think if you
follow the strategy above, you will still have the "strong" typing
when it is known at compile-time to be a call on a task entry,
whereas when it is a call on a class-wide interface object, you
will have to do extra work to come up with the parameters to pass
to the task or protected entry handlers.
> ... Also, since it is the entry call itself that
> implements Select statements, I don't quite see how to implement that for a
> call that never goes near the task supervisor (an ordinary procedure call).
Again, this new routine which handles interface'class "entry" calls
would figure out what to do.
> I don't think any of these is a huge deal for Janus/Ada. At worst, they'd be
> a performance hit for select statements, which is unlikely to matter to our
> customers. But I thought that other RTS's were much more aggressive about
> using this knowledge. For instance, I thought that the idea behind ceiling
> locking was to avoid all of the overhead of PO's completely, turning it all
> into a normal subprogram call. If that's done, I don't see how they'll be
> able to support a call that might be a task entry or might be a protected
> entry or might be neither, because the details are vastly different.
With the above approach, full optimization can be used for existing
calls. The only overhead would be for calls on class-wide objects.
> The implementation I was suggesting for entries fulfilling procedures was to
> wrap them to make them into regular procedures. That we know how to do
> (because of renames), and there is no problem doing it. But I don't know how
> to wrap an entry (of either a task or protected type) and later call it as
> an entry (i.e. via a select statement). Ada 95 doesn't have access-to-entry,
> after all. And I especially don't know how to put that into a tag (for one
> thing, it won't fit).
I suspect that a way around this is not only "wrap" the entries
and protected subprograms with regular subprograms, but also
wrap the task or protected object in a tagged record object.
Hence, a task or protected type that implements an interface
is actually internally represented as a tagged record with
one component, which is a task or protected object.
In other words, such task and protected types have their
existing layout, plus a tag added on the front.
The "wrapper" subprograms would have no trouble skipping past
this tag, implicitly selecting the task/protected component.
Similarly, any direct call of a protected subprogram or entry
would also implicitly select the task/protected component.
Converting back from a task/protected object to a class-wide
object would involve "backing up" to the tag preceding
the component.
> So, I think we need to talk to some other implementers about this further
> before spending too much time on it. If there isn't much problem, this is a
> vastly preferable solution to any of the others suggested. But I'd be
> surprised if the real-time folks think it is implementable.
>
> Which it why I suggested restricting it to protected types. In that case,
> select calls could be implemented however they are for protected entries,
> and one probably could figure out some way to map normal subprograms to
> that. (It would still be tough in our case, because the profiles wouldn't
> match...but probably surmountable.)
If you have to be able to handle protected entries, protected subprograms,
and regular subprograms, handling task entries as well seems like
a very small incremental effort, especially given the approach
suggested above.
> ... And it would be easier still with
> abstract entries.
True, though that would ultimately be less flexible,
and more syntax invention.
****************************************************************
From: Randy Brukardt
Sent: Friday, January 23, 2004 10:11 PM
Tucker said:
...
> I'm not sure what complexity you are talking about now.
The conceptual complexity of having operations match both ones inside the
protected or task type and outside the protected or task type. But I don't
feel that strongly about this. (And there is no doubt it would make concrete
types easier to write.)
...
> > Of course, if it proves not to make much work either in wording or in
> > implementation, then I won't object. But I have to wonder...
>
> I can't imagine how it would be more work in the implementation.
> And in the wording, it means simply allowing what is already
> allowed, namely overriding of an operation inherited from
> an ancestor. We would have to write more to disallow it,
> I suspect.
If true, go for it. :-)
> > ...
> > Tucker is correct here in terms of value. The main reason I didn't suggest
> > this was implementation complexity.
>
> A wrapper is a wrapper. It doesn't matter whether the wrapper
> is calling a task entry or a protected entry as far as I can
> tell.
That presumes you can figure out a way to write a wrapper that can call
either. At least not from a select statement. I can do it for Janus/Ada, but
it is very messy. It's not clear to me that it can be done for other
compilers - at least not with the efficiency that real-time users will
demand.
> > This would eliminate that, and require the task supervisor to somehow be
> > able to determine the difference between a task and a protected object at
> > runtime. ...
>
> I don't think this is the simplest way to support this.
> I would expect that you would leave the existing code pretty
> much as is, so if you know you are calling a task entry, you
> would call directly to the task entry handling code, and
> if you know you are calling a protected entry, you would
> call directly to the protected entry, but if you don't know
> whether you are actually calling an entry at all, or whether
> it is a task entry or a protected entry, then you would call
> a new routine, which would at run-time decide which case it
> has, and *then* call the appropriate handler. This would
> mean no added overhead for existing code; only code that
> called "interface'Class.Operation()" in a select statement would
> have to incur the overhead of deciding whether we had a task entry,
> a protected entry, a protected subprogram, or a "regular"
> subprogram.
That's what I was describing: how to write that new routine. It certainly
isn't possible in the current Janus/Ada RTS (there isn't any way to tell a
protected object from a task object at runtime - neither has a tag or other
static descriptor). So you have to change the way all tasks and all POs are
generated to add something that can tell that. (You could guess, of course,
and it *usually* would work [because POs are on the finalization chain], but
I don't think that's a good idea.)
> > ... And we'll also lose the strong typing of the entry definition
> > (clearly, we can stick a small integer into an address, but we'll no longer
> > know which it originally was).
>
> I'm not sure what you mean by "strong" typing. I think if you
> follow the strategy above, you will still have the "strong" typing
> when it is known at compile-time to be a call on a task entry,
> whereas when it is a call on a class-wide interface object, you
> will have to do extra work to come up with the parameters to pass
> to the task or protected entry handlers.
I'm referring to the strong typing of the task supervisor. Certainly, once
you punch a giant hole in it, the fact that the other paths remain strongly
typed is much less valuable.
> > ... Also, since it is the entry call itself that
> > implements Select statements, I don't quite see how to implement that for a
> > call that never goes near the task supervisor (an ordinary procedure call).
>
> Again, this new routine which handles interface'class "entry" calls
> would figure out what to do.
That's getting to be a pretty brilliant routine. Perhaps it will find bugs
in your program, too?? :-)
...
> With the above approach, full optimization can be used for existing
> calls. The only overhead would be for calls on class-wide objects.
Right. But I think the real-time folks will care about that cost. Unless, of
course, they never use the feature (in which case, why the heck did they ask
for it in the first place??)
(Of course, there is precedent for them asking for an
impossible-to-implement efficiently feature -- remember multi-way entry
calls??)
> > The implementation I was suggesting for entries fulfilling procedures was to
> > wrap them to make them into regular procedures. That we know how to do
> > (because of renames), and there is no problem doing it. But I don't know how
> > to wrap an entry (of either a task or protected type) and later call it as
> > an entry (i.e. via a select statement). Ada 95 doesn't have access-to-entry,
> > after all. And I especially don't know how to put that into a tag (for one
> > thing, it won't fit).
>
> I suspect that a way around this is not only "wrap" the entries
> and protected subprograms with regular subprograms, but also
> wrap the task or protected object in a tagged record object.
> Hence, a task or protected type that implements an interface
> is actually internally represented as a tagged record with
> one component, which is a task or protected object.
> In other words, such task and protected types have their
> existing layout, plus a tag added on the front.
> The "wrapper" subprograms would have no trouble skipping past
> this tag, implicitly selecting the task/protected component.
> Similarly, any direct call of a protected subprogram or entry
> would also implicitly select the task/protected component.
> Converting back from a task/protected object to a class-wide
> object would involve "backing up" to the tag preceding
> the component.
Yes, that works fine for calling entries as procedures. But once you do
that, you've lost access to the "entryness" of them. If you call that
wrapper in a select statement (via a class-wide call), it would look just
like a procedure to the compiler. It would have to, so it could be *called*
like a procedure. If you made all subprograms in interfaces callable as
entries, you'd have to implicitly add a boatload of parameters to them, and
that would be distributed overhead for all of the applications that *don't*
use select statements and entry calls.
> If you have to be able to handle protected entries, protected subprograms,
> and regular subprograms, handling task entries as well seems like
> a very small incremental effort, especially given the approach
> suggested above.
Quite possibly. I'm afraid the entire idea is impossible.
> > ... And it would be easier still with abstract entries.
>
> True, though that would ultimately be less flexible,
> and more syntax invention.
I certainly agree about more syntax invention. But at least I can see how to
implement class-wide calls of it.
---
Let me step back a moment and make sure that I understand your model for
these select statements of class-wide procedure calls.
Using a select statement to call a real procedure is clearly useless: the
procedure is always ready, so the timeout or alternative will never run. But
of course allowing that means that if it *is* an entry, then we have the
timeout available -- which means that we want entry behavior in that case.
A limited interface will have a tag of some sort, which will have the
addresses of the various wrapper routines generated. These will be normal
functions and procedures, and will be callable as such. (If not, you have
horrible distributed overhead on interfaces.) That's true even if the actual
operation is an entry. We know how to write these wrappers -- it's just
renames of an entry to a procedure (an existing Ada feature).
I hope we agree so far.
Now, you want to be able to make class-wide calls on procedures via select
statements. If the calls are class-wide, the actual routine will be looked
up via the tag. Right? In which case, you're going to get the wrapper which
is a procedure. Which is fine, except that that wrapper is going to *act*
like a procedure -- it is always ready. How could it not be? The wrapper is
just an ordinary subprogram, and it cannot provide the information necessary
to query if a task is waiting at an appropriate accept statement or if POs
barrier is open. All you have is a single subprogram pointer - to a
subprogram that must be callable as a normal subprogram.
I can think of ways to provide the necessary information, but they all have
a distributed overhead. That's because any interface procedure can be called
this way, and the code generated for a select statement needs to have some
way to figure out if (a) this is an ordinary subprogram; or (b) this is a
task entry, and its entry identifier; or (c) this is a protected entry, and
its entry body address. Since you want to have ordinary subprograms for
these as well, even inspection of the object won't work. You could put that
into the wrappers somehow, but then you'd have to put that into *every*
subprogram, or generate wrappers for *every* subprogram that's in an
interface (because that information would have to exist for any possible
call). Either of those would have nasty caching effects, and extra wrappers
mean slower calls as well.
I'm not even convinced that the above information is sufficient to make an
entry call. It certainly isn't if families are involved, but I'm assuming
we're not matching those here. Parameter passing is actually different in
Janus/Ada for protected entries, because we insert the protected object and
family info (or 0 if there is no family) in every call. So we have an
incorrect parameter list, and to fix it, we'd need to know how many
parameters there are -- meaning another piece of data. (And one that
Janus/Ada doesn't currently even give to the RTS.)
Anyway, I just don't see how to implement this without distributed overhead
of some sort. And that's based on our implementation, which is relatively
simple. It seems like it would be much worse for systems that actually cared
about performance instead of using all of these lookup values.
So, I'd like to hear where you expect to find the information needed to make
an entry call when all you have is the address of an ordinary procedure.
Perhaps you have some other implementation model in mind - please explain it.
****************************************************************
From: Tucker Taft
Sent: Friday, January 23, 2004 8:38 PM
Randy Brukardt wrote:
> Tucker said:
>
>>A wrapper is a wrapper. It doesn't matter whether the wrapper
>>is calling a task entry or a protected entry as far as I can
>>tell.
>
>
> That presumes you can figure out a way to write a wrapper that can call
> either.
I think the issues might have gotten mixed together.
First I was arguing for allowing task types
to implement limited interfaces, without talking
about select statements. My claim is that all you
need is a compiler-generated wrapper for any primitive
that is overridden by a task entry, and the wrapper
can just as easily call a task entry as a protected entry.
Later comes the discussion about select statements (see
more below)...
> ...
> So, I'd like to hear where you expect to find the information needed to make
> an entry call when all you have is the address of an ordinary procedure.
> Perhaps you have some other implementation model in mind - please explain
> it.
Ok, I'll propose something more concrete. And then
I'll put it into a new version of the AI...
Given a select statement like:
select
Sync_Obj.Add_Item(X);
Put_Line("Add_Item completed");
or
delay 5.0;
Put_Line("Add_Item timed out");
end select;
where "Sync_Obj" is a task or protected object,
we currently translate this into, roughly:
Status : Boolean;
Param_Block : constant Add_Item_Params := (X => X);
begin
System.RTS.Timed_Rel_{Protected,Task}_Call(
Called_Obj => Sync_Obj'Address,
Params => Param_Block'Address,
Name_Index => ,
Member_Index => 0, -- unless is member of entry family
Delay_Amount => 5.0,
Status => Status);
if Status = True then
Put_Line("Add_Item completed");
else
Put_Line("Add_Item timed out");
end if;
Timed_Rel_{Protected,Task}_Call sets Status to True if the
entry call completes, False if it times out.
To support a case where Sync_Obj is of type
Limited_Interface'Class, and Add_Item is
a primitive of Limited_Interface (called using
prefix notation), it could instead be translated
(roughly) into:
Sync_Obj : Limited_Interface'Class
...
type Call_Status is (Not_An_Entry, Completed, Not_Completed);
Status : Call_Status := Not_An_Entry;
Param_Block : constant Add_Item_Params := (X => X);
begin
Sync_Obj._Selective_Entry_Call(
Params => Param_Block'Address,
Slot_Num => ,
Selective_Call_Info => (Kind => Timed_Rel, Delay_Amount => 5.0),
Status => Status); -- assume Status is now an in-out param
if Status /= Not_Completed then
if Call_Status = Not_An_Entry then
Add_Item(Sync_Obj, X); -- Not an entry,
-- call "normal" overriding
end if;
Put_Line("Add_Item completed");
else
Put_Line("Add_Item timed out");
end if;
Every limited interface would have to have one
"implicit" primitive, say, _Selective_Entry_Call,
which by default is null, leaving the Status as
Not_An_Entry.
However, if the interface is implemented by a
task or protected type, and one of the interface's
primitives is overridden by an entry, then
_Selective_Entry_Call would have to be overridden
with something which checked the slot number
passed in, and if it corresponded to a primitive
that was overridden by an entry, it would pass
the name/family index of that entry to the appropriate
RTS routine along with the Param_Block and the
appropriate extra selective entry information,
such as the relative delay amount. The status
of this call would then determine the status of
the call on _Selective_Entry_Call.
If the primitive did not correspond to an entry,
then the Status would be left as Not_An_Entry,
and the compiler-generated code at the call site
would then do a "normal" call on the overriding
of the primitive, which might or might not be a wrapper.
As far as distributed overhead, it would mean that
every limited interface would need an implicit
"null" procedure corresponding to _Selective_Entry_Call.
This would purely be a space overhead, since it
would never be called if the user didn't take advantage
of this ability to call limited interface primitives
in a select statement.
If they did make such a call, and it did happen to
correspond to an entry, then the overriding of
_Selective_Entry_Call could make an "efficient"
call on the appropriate RTS routine passing in
the appropriate entry name/family indices, etc.
If it didn't correspond to an entry, then
_Selective_Entry_Call would return immediately to
the compiler-generated code which would do a normal
dispatching call on the primitive.
So there wouldn't be too much overhead even when
the feature was used.
****************************************************************
From: Randy Brukardt
Sent: Monday, January 26, 2004 7:04 PM
...
> Ok, I'll propose something more concrete. And then
> I'll put it into a new version of the AI...
...
Thanks for showing a possible implementation.
Unfortunately, such an implementation wouldn't work for Janus/Ada (at least
without a lot of modification to the runtime.)
Janus/Ada puts the (user) parameters on the stack in the normal way, then
passes the task supervisor parameters in registers to keep the two sets of
parameters separate. (For protected entry calls, the PO and family info is
duplicated, and passed both ways.) There is no separate "parameter block"
pointer - we don't have enought registers on the Intel processors to have
one.
The call to _Selective_Entry_Call, would, by definition be a normal call
with parameters pushed on the stack (as well as the return address). That
means when it came to be time to make the actual call, the parameters would
be in the wrong place. And you can't move them to the right place, because
they don't "belong" to the current subprogram and thus aren't accessible.
We'd also have problems because the second parameter of a PTE call is always
the family number, and that wouldn't be part of the profile.
I suspect that we'd be better off putting the entry info directly into the
tag:
type Interface_Tag_Entry_Kind is (Subprogram, Task_Entry,
Protected_Entry);
type Interface_Tag_Entry (Kind : Tag_Entry_Kind) is
Subprogram_Address : System.Address; -- The address of the subprogram
(wrapper),
-- for normal calls.
case Kind is
when Subprogram => null;
when Protected_Entry => Entry_Body : System.Address;
when Task_Entry => Entry_Id : Entry_Index;
end case;
end record;
(I think this could be done with two address slots per entry on most
targets, because most machines don't allow code near the zero address. So
you could tell a protected entry from a task entry by the size of the value,
and null (0) would be normal procedure. And in any case, there are not a lot
of tags out there, so it wouldn't be a huge disaster even if it needed three
dwords.) Note: This only would be for interface tags; regular tagged tags
wouldn't include this information (they can only include regular
subprograms).
Then generating something like the following for a select statement (this is
the same example as Tucker's):
declare
My_Slot : Tag_Entry renames Sync_Obj'Tag();
Status : Call_Status := Completed;
begin
if My_Slot.Kind = Subprogram then
My_Slot.Subprogram_Address.all; -- Call with above parameters.
elsif My_Slot.Kind = Task_Entry then
Status := Sup_Entry_Call (Kind => Timed_Call,
Callee => Sync_Obj,
Entry_Id => My_Slot.Entry_Id,
Family_Offset => 0,
Relative_Time => 5.0);
-- The parameters here are in registers.
else -- Protected_Entry
Status := Sup_Entry_Call (Kind => Timed_Call,
Callee => Sync_Obj,
Entry_Body => My_Slot.Entry_Body,
Family => 0,
Relative_Time => 5.0);
-- The parameters here are in registers.
end if;
This is bigger at the selective call site for a class-wide call, but only
there. It does mean generating the code to evaluate the parameters three
times, but it only will execute once on any given call, so that ought not be
too bad (it would have caching effects). And the call selected would be
nearly as efficient as a direct call would be (and could be optimized as a
direct call, as all of this would be explicitly in the intermediate code)).
This all presumes that everything that determines the call is known when the
tag is generated. I think that's true.
****************************************************************
From: Tucker Taft
Sent: Monday, January 26, 2004 7:45 PM
Glad to hear there is a possible way of handling this.
Your approach of putting one set of parameters in
registers and the other in the stack is clever.
We don't have the luxury of playing such games since
we are generally trying to hook into backends that
are not Ada-specific, and have relatively conventional
parameter passing conventions.
In our model, entry bodies and protected subprogram
bodies expect their parameters to all be bundled
into a single record, the so-called parameter block.
This simplifies requeue, among other things. Your
approach is probably more efficient in the typical case...
****************************************************************
From: Randy Brukardt
Sent: Monday, January 26, 2004 7:50 PM
> Your approach of putting one set of parameters in
> registers and the other in the stack is clever.
> We don't have the luxury of playing such games since
> we are generally trying to hook into backends that
> are not Ada-specific, and have relatively conventional
> parameter passing conventions.
We've done that as well, of course. The logical model is that the two sets of
parameters are separate. Sometimes you're forced into putting them together,
which is a real pain to straighten out again...
> In our model, entry bodies and protected subprogram
> bodies expect their parameters to all be bundled
> into a single record, the so-called parameter block.
> This simplifies requeue, among other things. Your
> approach is probably more efficient in the typical case...
Requeue is a mess, but the primary problem is hidden generic parameters (for
sharing), which might have to disappear or change or appear. "Disappear" is
easy enough, but I've never come up with a way to do the other cases. You can't
copy the parameters, because we use value-result passing for small scalar
types. And you certainly can't write over memory that you don't own! (We simply
refuse to compile problem requeues - not a lot of complaints to date on that.)
(This is one the places where a number of reasonable appearing choices work
together to make something impossible to implement. C'est la vie.)
****************************************************************
From: Tucker Taft
Sent: Sunday, January 25, 2004 1:29 PM
Here is version 4 of Task/Protected Interfaces.
It might better be called "implementing interfaces with task
or protected types" now, since I have dropped the special
syntax for task and protected interface types, and defined
rules for implementing the primitives of limited
interface types using entries and protected subprograms.
Significantly fewer RM sections are affected because of this change,
and I think it also provides better integration between
tagged, task, and protected types.
Comments, flames, etc., are encouraged.
****************************************************************
From: Randy Brukardt
Sent: Monday, January 26, 2004 7:45 PM
> Here is version 3 of Task/Protected Interfaces.
We seem to have two version 3s. (That's because we had two version 2s.) This
one will be numbered version 4 when posted.
> It might better be called "implementing interfaces with task
> or protected types" now, since I have dropped the special
> syntax for task and protected interface types, and defined
> rules for implementing the primitives of limited
> interface types using entries and protected subprograms.
>
> Significantly fewer RM sections are affected because of this change,
> and I think it also provides better integration between
> tagged, task, and protected types.
>
> Comments, flames, etc., are encouraged.
Well, except for select statements (which is new, of course), it certainly
seems simpler. And it provides the grand integration of real-time and OOP
features that we've been striving for. And it doesn't look like much
incremental work over that needed to implement any interfaces.
Indeed, it provides the "glue" that helps justify Interfaces themselves and
also the prefix call notation of AI-252. (You certainly want to be able to
call class-wide entries in the prefix notation; given that these are "just"
procedures, it would be weird not be able to call class-wide interface
procedures the same way. And then it would be weird not to be able to call
other class-wide procedures the same way. And then it would weird not to be
able to call other class-wide subprograms the same way.)
It also makes me look at interfaces more favorably, because they not only
solve a rare problem (multiple interface inheritance) that I can't get too
excited about, but also the integration of protected types into O-O (and
bring along tasks as an added bonus), as well as giving us formal tasks and
protected types more or less for free. These are all things that have been
requested in the past.
****************************************************************
From: Robert I. Eachus
Sent: Monday, January 26, 2004 8:59 PM
I like the new approach, but I haven't looked through it thoroughly
yet. However....
>Requeue is a mess, but the primary problem is hidden generic parameters (for
>sharing), which might have to disappear or change or appear. "Disappear" is
>easy enough, but I've never come up with a way to do the other cases. You
>can't copy the parameters, because we use value-result passing for small
>scalar types. And you certainly can't write over memory that you don't own!
>(We simply refuse to compile problem requeues - not a lot of complaints to
>date on that.)
>
>(This is one the places were a number of reasonable appearing choices work
>together to make something impossible to implement. C'est la vie.)
My position is that this is a very reasonable 1.1.3(6) restriction.
(And I assume that the rest of the ARG feels the same way.) Not that I
think requeues are not important, just that this sort of extreme corner
case is what that paragraph is there for.
****************************************************************
From: Tucker Taft
Sent: Monday, January 26, 2004 11:43 PM
> ...
> Requeue is a mess, but the primary problem is hidden generic parameters (for
> sharing), which might have to disappear or change or appear. "Disappear" is
> easy enough, but I've never come up with a way to do the other cases. You
> can't copy the parameters, because we use value-result passing for small
> scalar types.
When using a parameter block, we simply include the address of a temp
as a component of the parameter block to deal with by-copy [IN] OUT
parameters.
> ... And you certainly can't write over memory that you don't own!
> (We simply refuse to compile problem requeues - not a lot of complaints to
> date on that.)
I presume most other vendors have solved this somehow.
I don't think we should bless a restricted requeue (despite
Robert Eachus' comment).
> (This is one the places were a number of reasonable appearing choices work
> together to make something impossible to implement. C'est la vie.)
Yes, that happens...
****************************************************************
From: Randy Brukardt
Sent: Tuesday, January 27, 2004 12:26 AM
Tucker said, replying to me:
> When using a parameter block, we simply include the address of a temp
> as a component of the parameter block to deal with by-copy [IN] OUT
> parameters.
"Simply"? Sure, you can selectively turn off value-result passing for all
entry calls. That has always seemed too much like admitting defeat to me.
(You can punt on 2nd down, too, but that doesn't make it a good idea...)
> > ... And you certainly can't write over memory that you don't own!
> > (We simply refuse to compile problem requeues - not a lot of complaints to
> > date on that.)
>
> I presume most other vendors have solved this somehow.
> I don't think we should bless a restricted requeue (despite
> Robert Eachus' comment).
They don't have shared generics. At least ones with protected and task types
declared in them. (The problem only can occur when doing an external requeue
to an object of a task or protected type declared in a generic unit from
some object whose type is not in that unit. This isn't common.)
But I certainly don't want to "bless" such a restriction. I just was
commenting that I didn't think that the "parameter block" really made much
difference in the difficulty of requeue either way. (The generic parameter
problem would occur either way.)
****************************************************************
From: Tucker Taft
Sent: Tuesday, January 27, 2004 1:09 AM
It helps a bit I think because the parameter block holds the
"primary" parameters to the entry, while the auxiliary
parameters such as the static link, generic instance
descriptors, entry index, etc., could be passed separately.
The "primary" parameters don't change upon requeue, whereas
the auxiliary parameters typically do.
****************************************************************
From: Robert I. Eachus
Sent: Tuesday, January 27, 2004 9:28 AM
>I presume most other vendors have solved this somehow.
>I don't think we should bless a restricted requeue (despite
>Robert Eachus' comment).
>
>
I didn't say we should bless them. But from Randy's description the
problem isn't with all requeues, just with calls to requeue where the
original task declaration is not visible and there are defaulted or
hidden parameters. If I understand correctly, the workaround is to add
an "unnecessary" with clause. (In fact Randy might want to investigate
whether he can add a dependence on the unit containing task declaration
instead of just giving up.) But if the reality is that in most cases
such an extra dependence is not necessary, I prefer the solution of
"forcing" the user to add it when necessary instead of adding it in lots
of unnecessary cases.
But in any case, I have trouble visualizing a program that would run
into the problem. So I certainly think that Randy's approach of waiting
until a customer runs into it makes sense.
****************************************************************
From: Pascal Leroy
Sent: Tuesday, January 27, 2004 4:14 AM
> Comments, flames, etc., are encouraged.
This version looks much better than the previous one for all the reasons
that Randy mentions (and I would add: much less syntax invention and
therefore less opportunities to quarrel about the syntax).
One thing that I don't like at all is the notion of integrating entry
families into the mix. It complicates the language description, it
complicates the implementation, and entry families are sufficiently
marginal that you can write the wrapper yourselves if you absolutely
need to.
Wearing my implementer's hat for a minute, the views of subprograms
introduced by AI 252 (where the first parameter is omitted) are quite
complicated for us to represent. Actually, I know how to represent
them, but I don't know how to do this efficiently from the standpoint of
the compilation process. Adding yet another view to the mix, where the
first two parameters are omitted, is going to make my life considerably
more complicated for little user benefit.
****************************************************************
From: Tucker Taft
Sent: Tuesday, January 27, 2004 7:35 AM
> ...
> One thing that I don't like at all is the notion of integrating entry
> families into the mix. It complicates the language description, it
> complicates the implementation, and entry families are sufficiently
> marginal that you can write the wrapper yourselves if you absolutely
> need to.
Yes, I agree that entry families don't fit cleanly into
the picture. I thought I ought to try to work them out,
but all along the way I felt like this might not be
worth the effort. Your point about creating a wrapper
is a good one. To preserve the ability to use selective
entry calls, I suppose it might even be possible to
"wrap" the entry family with a "regular" entry, so long as
you were willing to have the entry index also appear as an
additional (dummy) parameter to the entry family, and didn't
mind it coming last in the wrapper, as follows:
entry Family(for Prio in Priority_Type)(X : Integer; Y : out Integer;
Dummy_Prio : Priority_Type := Default_Prio) when Prio > Min_Prio is
begin
...
end Family;
entry Family_Wrapper(X : Integer; Y : out Integer;
Prio: Priority_Type) when True is
begin
requeue Family(Prio) with abort;
end Family_Wrapper;
If we go this route, it might deserve a note in the RM to
identify this (somewhat non-obvious ;-) approach to the user.
Interestingly, while thinking about handling entry families,
I had concluded it would probably be easier for the implementation
if the family index was the last parameter rather than the second parameter
in the "equivalent" primitive subprogram, but I didn't have
the nerve to suggest that...
I suppose we could make the above user-written wrapper a bit more elegant by
relaxing the rules for requeue, so that rather than an all or none
match of parameters, you could requeue on any new entry that had no
more parameters than the original entry, and whose profile
conformed with the original entry for as many parameters
as the new entry had. (This relaxation would be essentially free
in our implementation, but of course it might be painful
for others.)
> Wearing my implementer's hat for a minute, the views of subprograms
> introduced by AI 252 (where the first parameter is omitted) are quite
> complicated for us to represent. Actually, I know how to represent
> them, but I don't know how to do this efficiently from the standpoint of
> the compilation process. Adding yet another view to the mix, where the
> first two parameters are omitted, is going to make my life considerably
> more complicated for little user benefit.
I hear you...
****************************************************************
From: Gary Dismukes
Sent: Tuesday, January 27, 2004 12:38 PM
Tuck wrote:
> Interestingly, while thinking about handling entry families,
> I had concluded it would probably be easier for the implementation
> if the family index was the last parameter rather than the second parameter
> in the "equivalent" primitive subprogram, but I didn't have
> the nerve to suggest that...
Good instinct not to suggest it ;-) I agree with Pascal that the
business of matching for entry families is too much to stomach.
> I suppose we could make the above user-written wrapper a bit more elegant by
> relaxing the rules for requeue, so that rather than an all or none
> match of parameters, you could requeue on any new entry that had no
> more parameters than the original entry, and whose profile
> conformed with the original entry for as many parameters
> as the new entry had. (This relaxation would be essentially free
> in our implementation, but of course it might be painful
> for others.)
That also sounds to me like too much extra language complexity
for not enough benefit.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, January 27, 2004 4:02 PM
Interesting. I don't really care either way, but I don't see any particular
difference between wrapping entries and entry families (other than that the
latter is new, while you already have to do the former). Indeed, for
protected entry calls, we already pass the family value as the second
parameter, so this would map exactly to what we already do. (Which I doubt
would make any actual difference in the implementation of the wrapper.)
I don't believe Tucker is proposing anything other than how you could call
them thru an interface -- he's not proposing to allow such calls directly.
So, once the wrapper is generated, there is no further cost - the actual
calls are "normal" procedure calls.
All-in-all, I think Pascal's concern is off-base; unlike AI-252 views, this
special view of an entry family exists in only one place: matching of
interface routines to entities privitive on the type. So I don't see any
need for them to muck up the rest of the compiler.
There may be other good reasons to not bother (the extra language wording is
an obvious one), but I don't think implementation issues are signficantly
different than that for other entries.
****************************************************************
From: Tucker Taft
Sent: Tuesday, January 27, 2004 4:16 PM
I have suggested yet another (small ;-) change to the
syntax of "normal" interfaces. In the example
I constructed, it was too weird when one interface
was derived from another to have the first ancestor
interface name immediately follow the word "interface."
For lack of a better idea, I have inserted the word "and":
type Queue is
limited interface and Protected_Interface;
I would still rather see the word "new" appear for interfaces
derived from other ancestors, but this is perhaps an alternative
which reads reasonably well. For the record, I would still prefer:
type Queue is new Protected_Interface [and ...];
or
interface Queue [is new Protected_Interface [and ...]];
The second form emphasizes that these beasts are fundamentally
*interfaces*, and their use as types (e.g. of objects) is
very restricted. (Yeah, yeah, I know I am fighting an up-hill
battle. ;-)
****************************************************************
From: Randy Brukardt
Sent: Tuesday, January 27, 2004 5:24 PM
Tucker said:
...
> I would still rather see the word "new" appear for interfaces
> derived from other ancestors, but this is perhaps an alternative
> which reads reasonably well.
I still think that saying that interfaces are derived types, for any reason
(even brevity of RM wording) is a mistake. The only property that they share
with derived types is the inheritance of operations, and even that isn't
quite the same (because you can get homographs from several interfaces, and
those must not disappear as homographs normally do).
Each interface is fundamentally a new type, potentially with operations
inherited from a number of other interfaces. For this, I much prefer the old
"implements" terminology, because it makes clear what is happening. These
guys don't have "ancestors". They don't "derive" anything. They are a new
type that can in some circumstances be used in place of some other type.
That's it.
Anything that makes one interface more important than another (or look that
way) is fundamentally wrong.
> For the record, I would still prefer:
>
> type Queue is new Protected_Interface [and ...];
How do you tell a new interface from a new concrete type derived from an
interface? They both have the same syntax if the above is adopted.
I had wanted something like:
type Concrete_Type is tagged and Some_Interface ...
for new concrete types, but we decided (especially because of generic
contract issues) that derived types had to always make concrete types, and
thus the above is not needed.
Nothing has changed about that, so give it up, OK??
> or
> interface Queue [is new Protected_Interface [and ...]];
>
> The second form emphasizes that these beasts are fundamentally
> *interfaces*, and their use as types (e.g. of objects) is
> very restricted. (Yeah, yeah, I know I am fighting an up-hill
> battle. ;-)
All I'll say here is "yuck". Especially as this form would require that the
interfaces are program units (everything declared this way is currently a
program unit). That means that operations would have to be declared in the
scope of the interface, much like protected types.
(OK, I couldn't resist saying more. It's still "yuck".)
****************************************************************
From: Pascal Leroy
Sent: Wednesday, January 28, 2004 4:24 AM
Randy protested:
> All-in-all, I think Pascal's concern is off-base; unlike
> AI-252 views, this special view of an entry family exists in
> only one place: matching of interface routines to entities
> privitive on the type. So I don't see any need for them to
> muck up the rest of the compiler.
Beware of giving advice about other people's implementation!
My understanding is that, as part of the grand unification between OO
and real-time, we are trying to do the reverse of AI 252, i.e. make it
possible to call an entry using a procedure-like syntax. Let me take an
example (I hope I am getting the syntax right):
type Itf is interface;
procedure E (I : Itf; B : Boolean; X : Integer);
task type Tsk is new Itf with
entry E (Boolean) (X : Integer);
end Tsk;
T : Tsk;
Now obviously you can write a call using the Ada 83+95 syntax:
T.E (False) (3);
But I believe that (assuming the proper visibility) you are also able to
write:
E (T, False, 3); -- Legal? I think so.
Now it's not only a matter of matching for overriding purposes. In our
implementation we need a piece of Diana that describes the subprogram
that is invoked by this call. This subprogram has the profile:
procedure E (I : T; B : Boolean; X : Integer);
but it is actually a view of the entry T.E. Consequently, it must be
tied to T.E in some way, to indicate that it's just going to execute the
same code, with proper parameter massaging.
This in and of itself is not easy. I don't want to think of what
happens when Tsk is passed to a generic as an actual for a formal type
which is derived from Itf. We already go through numerous hoops to tie
the subprograms of the formal type to those of the actual type, and AI
252 is going to make things worse. I'd rather not add insult to injury
by putting entry families into the mix.
> There may be other good reasons to not bother (the extra
> language wording is an obvious one), but I don't think
> implementation issues are signficantly different than that
> for other entries.
Not only does this complicate the wording, but it is also very
arbitrary. That the first parameter becomes the prefix, as in AI 252,
seems rather natural. That the entry index becomes the 2nd parameter is
not at all intuitive to me.
****************************************************************
From: Pascal Leroy
Sent: Wednesday, January 28, 2004 4:27 AM
Tucker tried to sneak in a syntax change:
> NOTE: I have suggested yet another (small ;-) change to the
> syntax of "normal" interfaces. In the example I constructed,
> it was too weird when one interface was derived from another
> to have the first ancestor interface name immediately follow
> the word "interface." For lack of a better idea, I have
> inserted the word "and":
>
> type Queue is
> limited interface and Protected_Interface;
>
> I would still rather see the word "new" appear for interfaces
I realize that you have never been very satisfied with the syntax for
interfaces, but beware that, by periodically reopening this issue, you
are likely to cause lengthy discussions about the syntax that lead
nowhere, and could well kill the whole proposal.
****************************************************************
From: Tucker Taft
Sent: Wednesday, January 28, 2004 9:25 AM
> But I believe that (assuming the proper visibility) you are also able to
> write:
>
> E (T, False, 3); -- Legal? I think so.
I talked about this, but didn't include it in the proposal. In the
Version 3/4 AI-345 proposal, you could only do this via a dispatching
call through an interface. You wouldn't be able to do it directly.
In version 5, the idea of implementing primitives via entry families
was dropped.
Note that the idea of directly calling entries using "infix"
notation has not appeared in any of the versions of AI-345. It doesn't
seem to add sufficient benefit to justify the wording changes,
and in any case, it seems relatively separable from the current
proposal about implementing interfaces.
> ...
> Not only does this complicate the wording, but it is also very
> arbitrary. That the first parameter becomes the prefix, as in AI 252,
> seems rather natural. That the entry index becomes the 2nd parameter is
> not at all intuitive to me.
Yes, I agree making it the second parameter did feel a bit arbitrary.
As I noted, in our implementation, it would be easier to handle if
it were the last parameter. But despite Randy's view, I think it
makes sense to drop the idea of entry families implementing primitives.
Let's focus on what is actually written in version 5 of AI-345, and
see what people think of that.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, January 28, 2004 12:34 PM
> Randy protested:
>
> > All-in-all, I think Pascal's concern is off-base; unlike
> > AI-252 views, this special view of an entry family exists in
> > only one place: matching of interface routines to entities
> > privitive on the type. So I don't see any need for them to
> > muck up the rest of the compiler.
>
> Beware of giving advice about other people's implementation!
I totally agree. But...
> My understanding is that, as part of the grand unification between OO
> and real-time, we are trying to do the reverse of AI 252, i.e. make it
> possible to call an entry using a procedure-like syntax.
...now you're discussing the implementation difficulty of a feature that is
not in the proposal, and (as Tucker mentioned) is not necessary in any way
for the proposal.
Using the implementation difficulty of something that isn't even proposed to
object to something that is proposed should not be considered a valid
approach!!
While I don't see getting families integrated into this model as critical, I
do think it would be valuable if possible. That's especially true for
protected entry bodies, where the family index is really just an extra
parameter to the entry that can be used in the barrier. I've used them that
way on several occassions, and I certainly would not consider them "rare".
(Task families on the other hand, are hardly ever used.) Mapping protected
entry bodies with a family index to a subprogram with two extra parameters
seems exactly natural - that's really what they are, and all of the extra
'()'s and '.'s just get in the way of understanding what is going on.
I cannot imagine why anyone would think (implementation issues aside) that
the entry family of a protected entry would be anything other than the
second parameter. The call (Pascal's example using protected entries)
P.E (False) (3);
certainly makes sense to map to:
E (P, False, 3);
when P is an interface'class. I don't see any logic behind mapping it to
anything else:
E (P, 3, False);
reorders the parameters from their original order.
I'm a bit dismayed that Tucker decided to redo the AI to remove the family
stuff at the first sign of discomfort; in order to properly discuss these
issues, they need to be in the proposal. Otherwise, they either do not come
up at all, or we have a very muddled discussion with no concrete idea what
it is that we're talking about.
I'm especially concerned that we haven't heard from the real-time community
on this proposal at all. This is supposed to be for them (at least in part),
and it would make sense to find out what they think rather than killing off
things at the first sign of trouble...
(I have not posted "version 5" because it is unnecessary to do so; it only
deletes stuff without much justification, which in this case is trivial to
do. We're not going to adopt this AI in March anyway, so I don't see any
reason to rush here. We can make the trivial change of deleting the family
stuff if that is the decision at the meeting.)
****************************************************************
From: Gary Dismukes
Sent: Wednesday, January 28, 2004 1:14 PM
Randy wrote:
> All-in-all, I think Pascal's concern is off-base; unlike AI-252 views, this
> special view of an entry family exists in only one place: matching of
> interface routines to entities privitive on the type. So I don't see any
> need for them to muck up the rest of the compiler.
>
> There may be other good reasons to not bother (the extra language wording is
> an obvious one), but I don't think implementation issues are signficantly
> different than that for other entries.
My concern is mainly with the additional rule complexity for insufficient
benefit. Implementation isn't so much of a concern, though this does seem
to complicate resolution as well. It would be useful to know the perspective
of real-time users about how important this capability is.
****************************************************************
From: Pascal Leroy
Sent: Thursday, January 29, 2004 3:38 AM
Tuck clarified:
> > E (T, False, 3); -- Legal? I think so.
>
> I talked about this, but didn't include it in the proposal.
> In the Version 3/4 AI-345 proposal, you could only do this
> via a dispatching
> call through an interface. You wouldn't be able to do it directly...
>
> Note that the idea of directly calling entries using "infix"
> notation has not appeared in any of the versions of AI-345.
> It doesn't seem to add sufficient benefit to justify the
> wording changes, and in any case, it seems relatively
> separable from the current proposal about implementing interfaces.
I am happy to discuss this issue separately, but I think it is required
for consistency with the rest of the proposal. Consider an example
without entry families:
type Itf is limited interface;
procedure E (I : Itf; X : Integer);
task type Tsk is new Itf with
entry E (X : Integer);
end Tsk;
T : Tsk;
I would find it curious that the following dispatching call be legal:
E (Itf'Class (T), 3);
but not the following non-dispatching call:
E (T, 3);
After all, in Ada we try to favor static binding whenever possible, so
it would be strange to force dynamic binding in this particular
situation.
Furthermore, I believe that the implementation complexity is mostly
there anyway because of, lo and behold, generics. Augment the previous
example with:
generic
type Formal is new Itf with private;
procedure P (X : Formal);
procedure P (X : Formal) is
begin
E (X, 3);
end P;
procedure I is P (Tsk);
In order to generate the code for the body of instantiation I you need
some mechanism to tie the (subprogram) call on E to the entry Tsk.E
(including parameter massaging, internal representation in the symbol
table/Diana tree/whatever, etc.). So the only implementation complexity
that is saved by not allowing infix calls to entries is the one related
to name resolution (because you don't redo name resolution in generic
instantiations).
****************************************************************
From: Tucker Taft
Sent: Thursday, January 29, 2004 1:09 PM
> I am happy to discuss this issue separately, but I think it is required
> for consistency with the rest of the proposal. ...
You make good points. I don't think it is essential, but I agree
it makes sense to have the "language design principle" that
what you can do using dynamic binding you should be able
to do with a statically bound call, with essentially the same syntax.
This will actually simplify the AI-345 wording a bit in some places,
while probably requiring a bit more wording elsewhere.
****************************************************************
From: Tucker Taft
Sent: Thursday, January 29, 2004 1:48 PM
> This will actually simplify the AI-345 wording a bit in some places,
> while probably requiring a bit more wording elsewhere.
Oops, I got myself confused. The above principle does *not*
require that we make *all* entries callable using "infix"
notation (by "infix" I really mean putting the task or
protected object inside the parenthese, rather than out
front). The way AI-345 is written, the task or protected
type inherits a primitive subprogram from the interface,
and then at that point it is implicitly *implemented* by an
entry that conforms to it. It can still be overridden explicitly by
a "normal" primitive subprogram. And entries that *don't*
conform to some primitive inherited from the interface don't
"magically" become visible in the scope enclosing the
task/protected type.
[One partial analogy I can think of is the "=" operator for type
extensions, which is implicitly implemented to call the parent "="
and the predefined "=" on the components of the extension part,
but which can be overridden if desired.]
So really all that we need to allow is for these inherited
primitive subprograms (that happen to be implemented by
an entry or protected subprogram) to be called in the
"usual" way, i.e. all parameters inside the parentheses.
Which they will be unless we go out of our way to make it illegal.
We *don't* need to make other entries or protected subprograms
callable in this way, and I'm not convinced it is worth the
visibility fiddling that would be required to do so.
As far as the effect on AI-345, no change is needed to
allow inherited primitives to be called in the "usual" way,
presuming it is clear that they aren't "abstract" once
they have been implemented by an entry or protected subprogram.
I think the only change would be to the wording after 9.7.2(3)
for the "procedure_or_entry_call" construct which should also
allow the procedure to be a primitive of a task or protected type
so long as it is implemented by an entry.
So I guess I am back to agreeing with Randy that the real issue
remains generating wrappers for inherited primitives that are
"implemented" by entries or proteced subprograms. There
should be no additional compiler front end work, I don't think,
because the inherited primitives are presumably already in the
symbol table due to the usual type derivation rules. If we want
to allow primitives to be implemented with an entry family, then
that wouldn't suddenly *add* all entry family names to the enclosing
scope. It would simply mean that an inherited primitive, which
is already in the enclosing scope, now has an implicit body which
calls a member of the entry family.
On the other hand, if we wanted to allow *all* entries and
protected subprograms to be called in "infix" notation,
that would be a different matter, and I don't think I would
recommend that, and in that case, the entry family situation
would be harder to stomach.
Ultimately, I don't think the decision on entry families
is that important, since you can write your own wrappers,
which could be a single entry "wrapper" if need be. Again, we
could use some guidance from the real-time folks here...
****************************************************************
From: Randy Brukardt
Sent: Thursday, January 29, 2004 1:59 PM
Pascal said:
> > Note that the idea of directly calling entries using "infix"
> > notation has not appeared in any of the versions of AI-345.
> > It doesn't seem to add sufficient benefit to justify the
> > wording changes, and in any case, it seems relatively
> > separable from the current proposal about implementing interfaces.
>
> I am happy to discuss this issue separately, but I think it is required
> for consistency with the rest of the proposal.
...
> After all, in Ada we try to favor static binding whenever possible, so
> it would be strange to force dynamic binding in this particular
> situation.
Fair enough, but it wasn't what was proposed.
> Furthermore, I believe that the implementation complexity is mostly
> there anyway because of, lo and behold, generics. Augment the previous
> example with:
>
> generic
> type Formal is new Itf with private;
> procedure P (X : Formal);
>
> procedure P (X : Formal) is
> begin
> E (X, 3);
> end P;
>
> procedure I is P (Tsk);
>
> In order to generate the code for the body of instantiation I you need
> some mechanism to tie the (subprogram) call on E to the entry Tsk.E
> (including parameter massaging, internal representation in the symbol
> table/Diana tree/whatever, etc.). So the only implementation complexity
> that is saved by not allowing infix calls to entries is the one related
> to name resolution (because you don't redo name resolution in generic
> instantiations).
I don't see this. Any call thru an interface is (logically at least)
equivalent to a dispatching call. The compiler can do the lookup at
compile-time, but it's still (logically) dispatching. That means that what
this calls is the wrapper procedure; it's not a direct call to the entry.
Certainly the needed wrapper exists, and you know where to find it. You're
always welcome to work harder if you want, but I don't see any reason to
treat this call any different than any other subprogram call.
(Not to mention that there is no such thing as a statically bound call in a
generic sharing implementation. All calls are dynamically dispatching, the
only difference is where the tag comes from [it comes from the actual type
if the call would be statically bound]. And that implementation always works
for any compiler - if it's too hard to statically bind the call - and I
think that it is in this case - dispatch through the tag, being careful to
use the right tag.)
Ultimately, all of these questions boil down to how much work we're going to
make the user do so we (implementers) don't have to. The main reason that
I'm in favor of including support for entry families is that the work-around
(as described by Tucker) requires an extra entry and a dummy parameter on
the original entry -- and that hardly is going to increase readability and
correctness of Ada programs! What I don't know is whether protected entry
families are used as much in practice as they appear to be in theory. If
they are, we ought to support them properly here.
****************************************************************
From: Pascal Leroy
Sent: Monday, April 5, 2004 8:28 AM
[Editor's note: this is a comment on version /05.]
Nitpicking: I think you have an extraneous AND in 3.9.4(2). Compare
your syntax with that of AI 251.
Otherwise it looks good.
****************************************************************
From: Tucker Taft
Sent: Monday, April 5, 2004 10:23 AM
That was intentional. The examples I wrote looked weird
without some kind of connector between the word "interface"
and the first interface name, especially when there was
a qualifier like "task". So while I was "augmenting"
the syntax I added "and" as the connector. For example:
type T is task interface Comparable;
just looked weird if "Comparable" wasn't a task interface.
type T is task interface and Comparable;
made a bit more sense, and didn't seem to imply that Comparable
was itself a task interface.
> Otherwise it looks good.
That's good to hear.
****************************************************************
From: Pascal Leroy
Sent: Monday, April 5, 2004 10:41 AM
Makes sense.
****************************************************************
From: Tucker Taft
Sent: Tuesday, October 26, 2004 7:33 AM
John and Pascal have noticed a hole in the wording for
AI-345 where we don't clearly indicate the intent that
a nonlimited type may implement a limited interface.
This leads to the question of what determines whether
a derived type declared with one or more limited interface
ancestors is limited or nonlimited. The simplest rule
is that a derived type is nonlimited if at least one
of its ancestors is nonlimited. However, what if
you want to have a nonlimited type all of whose
interface ancestors are limited?
In thinking about this last night, I had a couple of
ideas. Presuming we like having limited interfaces
implemented by nonlimited types, then we need
a way to declare a nonlimited type all of whose
interface ancestors are limited. I realized that the simplest
solution would be to have a ready-made nonlimited
interface sitting around and just throw that in as
another interface ancestor. E.g.:
type My_Nonlimited_Type is new Lim_Int1 and Lim_Int2
and Nonlimited_Interface with private;
where Nonlimited_Interface is declared:
type Nonlimited_Interface is interface;
with *no* primitive operations.
This got me to thinking about the possibility of whether
we could imagine that *all* nonlimited tagged types had
such an interface as their ancestor. This would allow you
to create "very" polymorphic lists using
Nonlimited_Interface'class as the element type.
This naturally led me to thinking about an even more polymorphic
list, presuming we had a limited interface which every
tagged type, whether limited or non-limited, was considered
a descendant. The same could go for synchronized, protected,
and task. This led me to something like:
package Ada.Roots is
type Limited_Root is limited interface;
type Nonlimited_Root is interface and Limited_Root;
type Synchronized_Root is synchronized interface and Limited_Root;
type Task_Root is task interface and Synchronized_Root;
type Protected_Root is protected interface and Synchronized_Root;
end Ada.Roots;
I suppose all of these names could be inverted, producing
"Root_Limited," "Root_Nonlimited", etc., but since "limited,"
"synchronized", and "protected" are clearly adjectives, I
somewhat preferred the above order.
In any case, the important point is that *all* tagged types
would be considered to be covered by Limited_Root'Class,
all nonlimited tagged types would be covered by Nonlimited_Root'Class,
all protected types would be covered by Protected_Root'Class,
and all task types would be covered by Task_Root'Class.
In addition to "solving" the problem with a nonlimited
type with only limited interface ancestors, having these
root interfaces could be quite useful. Most O-O languages
have a root type, often called "Object." This can be
quite useful, and perhaps adding something like this should
be considered part of the Ada 2005 "rounding out" of the
O-O model. Note that allowing extensions at a nested scope
helps make this possible.
Having these might also simplify the creation of something like
the "generic constructor" we have proposed.
By the way, on the technical point of how to decide whether
a derived type is limited or nonlimited, an alternative
approach is to determine it based on the *first* (primary)
parent, and then consider it an error if a limited type
has a nonlimited ancestor. That might be friendlier to
the reader -- you would only have to look at the first
parent to determine the properties of the derived type.
So the above example would become:
type My_Nonlimited_Type is new Ada.Roots.Nonlimited_Root and
Lim_Int1 and Lim_Int2 with private;
A second point -- if we add these "root" types I would simplify
the rules for task and protected types and make them *all*
treated as "synchronized tagged" types, independent of whether they
have an explicit interface as an ancestor. Currently, only if
a task or protected type has an interface ancestor can you use the
'Tag attribute with it. The thought was that perhaps this would
provide a smoother transition from Ada 95, but having a "Task_Root"
interface seems so useful, that whatever marginal value there is
in leaving "old tasks" completely as is does not seem to be worth it.
I would like to fix the hole in AI-345 soon, so comments on
any of the above would be appreciated in the near future.
****************************************************************
From: Robert A. Duff
Sent: Tuesday, October 26, 2004 2:48 PM
I like it.
I'm not sure why you want to determine limitedness based only on the
*first* type. Using all of them seems cleaner to me. But I'm not sure
about that.
I very slightly prefer Root_XXX to XXX_Root. No big deal.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, October 27, 2004 9:41 PM
> John and Pascal have noticed a hole in the wording for
> AI-345 where we don't clearly indicate the intent that
> a nonlimited type may implement a limited interface.
> This leads to the question of what determines whether
> a derived type declared with one or more limited interface
> ancestors is limited or nonlimited. The simplest rule
> is that a derived type is nonlimited if at least one
> of its ancestors is nonlimited.
That seems fine.
...
> This naturally led me to thinking about an even more polymorphic
> list, presuming we had a limited interface which every
> tagged type, whether limited or non-limited, was considered
> a descendant. The same could go for synchronized, protected,
> and task. This led me to something like:
>
> package Ada.Roots is
> type Limited_Root is limited interface;
> type Nonlimited_Root is interface and Limited_Root;
> type Synchronized_Root is synchronized interface and Limited_Root;
> type Task_Root is task interface and Synchronized_Root;
> type Protected_Root is protected interface and Synchronized_Root;
> end Ada.Roots;
>
> I suppose all of these names could be inverted, producing
> "Root_Limited," "Root_Nonlimited", etc., but since "limited,"
> "synchronized", and "protected" are clearly adjectives, I
> somewhat preferred the above order.
>
> In any case, the important point is that *all* tagged types
> would be considered to be covered by Limited_Root'Class,
> all nonlimited tagged types would be covered by Nonlimited_Root'Class,
> all protected types would be covered by Protected_Root'Class,
> and all task types would be covered by Task_Root'Class.
>
> In addition to "solving" the problem with a nonlimited
> type with only limited interface ancestors, having these
> root interfaces could be quite useful. Most O-O languages
> have a root type, often called "Object." This can be
> quite useful, and perhaps adding something like this should
> be considered part of the Ada 2005 "rounding out" of the
> O-O model. Note that allowing extensions at a nested scope
> helps make this possible.
This seems interesting at first, until you start thinking about how this
could be used. Since there aren't any operations, there really isn't
anything useful that could be done with an object of Limited_Root'Class. It
doesn't have any operations or components! All you could do is test its type
and then convert it to some other type. That's awful O-O programming
(because it's not extensible) - it's just like a giant case statement.
Most O-O languages have some operations on their root type (like streaming,
hashing, etc.) that make this more interesting (because there are useful
things that you can do with the type).
Anyway, it seems pretty late for such a change, especially as we considered
something on this line (but not as ambitious) before and rejected it.
> Having these might also simplify the creation of something like
> the "generic constructor" we have proposed.
I don't see how. The operations are the key to that, and there aren't any
operations here.
...
> A second point -- if we add these "root" types I would simplify
> the rules for task and protected types and make them *all*
> treated as "synchronized tagged" types, independent of whether they
> have an explicit interface as an ancestor. Currently, only if
> a task or protected type has an interface ancestor can you use the
> 'Tag attribute with it. The thought was that perhaps this would
> provide a smoother transition from Ada 95, but having a "Task_Root"
> interface seems so useful, that whatever marginal value there is
> in leaving "old tasks" completely as is does not seem to be worth it.
You'd have to do that, or you won't be able to treat an ordinary task object
as a member of Limited_Root'Class.
> I would like to fix the hole in AI-345 soon, so comments on
> any of the above would be appreciated in the near future.
Gotcha.
****************************************************************
From: Pascal Leroy
Sent: Friday, October 29, 2004 7:02 AM
> > package Ada.Roots is
> > type Limited_Root is limited interface;
> > type Nonlimited_Root is interface and Limited_Root;
> > type Synchronized_Root is synchronized interface
> and Limited_Root;
> > type Task_Root is task interface and Synchronized_Root;
> > type Protected_Root is protected interface and
> Synchronized_Root;
> > end Ada.Roots;
I agree with Randy's comments on this idea. I'd like to add two things:
I fear that by introducing the fiction that all types are derived from an
interface, we would impose a distributed overhead on implementations. If
all tasks must have the baggage associated with an interface, that can be
a problem. Another example is that interfaces don't work well with
user-defined tag placement, and we don't want to make user-defined tag
placement invalid for existing tagged types.
Also, interfaces have the nasty "no hidden derivation" rule. If I have a
limited private type that is implemented by a nonlimited type, is the full
view derived from Nonlimited_Root? Does that mean that the partial view
has to be derived from Nonlimited_Root too? One would assume that this
would be necessary if Nonlimited_Root has any primitive operation ("="
comes to mind).
While it is clear that we need an answer to the question of how
limitedness of interfaces carries to concrete types, Tuck's proposal
doesn't seem like the most productive approach to me.
****************************************************************
From: Tucker Taft
Sent: Friday, October 29, 2004 8:28 AM
> I fear that by introducing the fiction that all types are derived from an
> interface, we would impose a distributed overhead on implementations. If
> all tasks must have the baggage associated with an interface, that can be
> a problem. Another example is that interfaces don't work well with
> user-defined tag placement, and we don't want to make user-defined tag
> placement invalid for existing tagged types.
Can you remind me of the problem? If you have user-defined tag placement,
couldn't you just provide a dispatching version of "'Tag" in the same
way we have dispatching versions of "'Size" for classwide_obj'Size?
>
> Also, interfaces have the nasty "no hidden derivation" rule. If I have a
> limited private type that is implemented by a nonlimited type, is the full
> view derived from Nonlimited_Root? Does that mean that the partial view
> has to be derived from Nonlimited_Root too? One would assume that this
> would be necessary if Nonlimited_Root has any primitive operation ("="
> comes to mind).
I think we should probably refine the "no hidden derivation" rule anyway.
What we really care about is that for types that can be extended *and*
that have an interface with at least one primitive operation, we want
such interfaces to be visible. If the type can't be extended (e.g.
it is a task or protected type, or is not visibly tagged), or if the
interface has no primitives, then there is no reason to impose this rule.
You bring up the Nonlimited_Root which would have "=" (and ":=").
We don't want that to be hidden, I agree. But we already require that
a limited tagged private type be implemented by a limited full type.
There are good reasons for that (you don't want a root-type's non-limitness
to be hidden, since then extenders might add uncopyable component's in
an extension part).
Certainly a non-tagged private type need not advertise that its
full type (implicitly) implements some interface, since it can't
be extended anyway.
> While it is clear that we need an answer to the question of how
> limitedness of interfaces carries to concrete types, Tuck's proposal
> doesn't seem like the most productive approach to me.
I would like you to reconsider. It is clear that having a Task_Root
would be useful, as it would remove a lot of the need for the
Task_Identity kludges. Having a Nonlimited_Root solves the problem
of defining a non-limited type with only limited interface ancestors,
and also is useful, since it has the "=" and ":=" operations.
Admittedly Limited_Root is not very useful, but it does provide
an ability to create completely polymorphic structures, which despite
concerns about violating "good" O-O style, can still be useful,
for example when doing storage management, and wanting to maintain
a list of all allocated objects. Clearly 'Size would still work
on Limited_Root'Class.
Even O-O languages that have generics have still found having a type
that represents the overall "root" of the extendable type hierarchy
is useful. You could define non-generic interfaces for "magic" builtin
operations such as "hash" or "image" which implementations might want
to provide, or which we might want to standarize some day. Or we
could add overridable primitives to these root types some day for
similar purposes. I think as we look toward the next 10 years, we
have the goal of making sure the language is flexible and extensible,
and will compete well with other O-O languages. Having these root
types could be seen as "rounding" out the O-O capabilities, and
having useful distinctions such as Synchronized_Root or Task_Root
would give Ada a leg up in this area over languages like Java which
don't really deal with multi-thread sychronization in a clean, safe way.
****************************************************************
From: Tucker Taft
Sent: Friday, October 29, 2004 8:42 AM
> Can you remind me of the problem? If you have user-defined tag placement,
> couldn't you just provide a dispatching version of "'Tag" in the same
> way we have dispatching versions of "'Size" for classwide_obj'Size?
I'll answer my own question: Duh. No, you can't dispatch
without knowing where the tag is. So a dispatching version
of 'Tag doesn't make any sense. One thing you
could do is have a dispatching version of "'Address" which
would require that you pass a pointer to the word containing
the tag, but you could dispatch to find out where the object
actually "started" if the tag wasn't the first word of the object.
That might actually work.
I hate to have user-defined tag placement derail interesting
ideas, unless it is really an important capability. Do you
have any idea how often and for what purpose it is used?
****************************************************************
From: Pascal Leroy
Sent: Friday, October 29, 2004 9:01 AM
> Can you remind me of the problem? If you have user-defined
> tag placement, couldn't you just provide a dispatching
> version of "'Tag" in the same way we have dispatching
> versions of "'Size" for classwide_obj'Size?
Say that type T implements interface I, and the tag of T is placed at
offset 40. When you convert an object of type T to type I, how does the
interface know where the tag is located? I seem to remember that your
compiler does not support user-defined tag placement, but others do, and
this is not a capability we can afford to lose. It's one thing to say "if
you use interfaces, say goodbye to tag placement". It's a totally
different thing to say "you are always using interfaces implicitly, so no
tag placement in Ada 2005".
> If the type can't be extended (e.g.
> it is a task or protected type, or is not visibly tagged), or
> if the interface has no primitives, then there is no reason
> to impose this rule.
I agree that it makes sense to relax the rule for types that cannot be
extended. I am not convinced that it is a good idea to special-case the
interface that has no primitive. It seems like a maintenance headache to
me (add a primitive, and your architecture need extensive surgery).
> I would like you to reconsider. It is clear that having a
> Task_Root would be useful, as it would remove a lot of the
> need for the Task_Identity kludges.
Hardly a convincing argument. Attribute Identity is here to stay, so to
take advantage of the Task_Root interface, we would need to provide a
second mechanism to name tasks. That would increase confusion.
> Having a Nonlimited_Root
> solves the problem of defining a non-limited type with only
> limited interface ancestors, and also is useful, since it has
> the "=" and ":=" operations.
Hardly a convincing argument, again. If we are trying to solve the
problem of defining a non-limited type with only limited interface
ancestors, all we need to do is to provide somewhere a nonlimited
interface. We don't have to say that all nonlimited types implement that
interface.
> Even O-O languages that have generics have still found having
> a type that represents the overall "root" of the extendable
> type hierarchy is useful.
Not all OO languages are like that (think of C++). I guess I have never
seen this approach as very elegant language design. When your only
abstraction mechanism is inheritance, you tend to solve all problems in
terms of inheritance.
--
Couldn't we simply say that a type whose parents are all interfaces does
not inherit limitedness, but that limitedness must be explicitly
specified? After all that's what we do for task and protected types: you
have to repeat "task" or "protected", even if you ancestors are all task
or protected interfaces. Thus, if I1, I2, I3 are limited interfaces, then
in the following declarations:
type T1 is new I1 and I2 and I3 with ...;
type T2 is limited new I1 and I2 and I3 with ...;
type T1 is nonlimited and type T2 is limited. The second form would of
course be illegal if any of the interfaces is nonlimited. We could even
allow repeating "limited" if the first ancestor is a noninterface limited
type, just for documentation.
****************************************************************
From: Pascal Leroy
Sent: Friday, October 29, 2004 9:12 AM
> I'll answer my own question: Duh. No, you can't dispatch
> without knowing where the tag is. So a dispatching version
> of 'Tag doesn't make any sense. One thing you could do is
> have a dispatching version of "'Address" which would require
> that you pass a pointer to the word containing the tag, but
> you could dispatch to find out where the object actually
> "started" if the tag wasn't the first word of the object.
There's no problem that cannot be solved with a level of indirection ;-)
> I hate to have user-defined tag placement derail interesting
> ideas, unless it is really an important capability. Do you
> have any idea how often and for what purpose it is used?
I don't remember the details now, but when we didn't support this, we had
customers clamoring for it. Not to mention that many compilers support it
in one way or another. I think that users just want to get the tag out of
the way. Maybe the Master of Claw can provide more information.
****************************************************************
From: Tucker Taft
Sent: Friday, October 29, 2004 9:23 AM
> I agree that it makes sense to relax the rule for types that cannot be
> extended. I am not convinced that it is a good idea to special-case the
> interface that has no primitive. It seems like a maintenance headache to
> me (add a primitive, and your architecture need extensive surgery).
In Java, there are several operation-less interfaces which are
used as "indicators." I'm not sure whether that would become a
common paradigm in Ada, but if it did, it would seem that it
would be reasonable for such indicators to be private if desired.
I think going from an interface without operations to one
with would require extensive surgery any way you "slice" it. ;-)
> Couldn't we simply say that a type whose parents are all interfaces does
> not inherit limitedness, but that limitedness must be explicitly
> specified? After all that's what we do for task and protected types: you
> have to repeat "task" or "protected", even if you ancestors are all task
> or protected interfaces. Thus, if I1, I2, I3 are limited interfaces, then
> in the following declarations:
>
> type T1 is new I1 and I2 and I3 with ...;
> type T2 is limited new I1 and I2 and I3 with ...;
>
> type T1 is nonlimited and type T2 is limited. The second form would of
> course be illegal if any of the interfaces is nonlimited. We could even
> allow repeating "limited" if the first ancestor is a noninterface limited
> type, just for documentation.
This seems like a viable idea. I might recommend placing the "limited"
next to the word "private" or "record" rather than next to new, since
"limited" comes after "tagged" now, and moving it to the front here
might cause "cognitive dissonance" for users. Hence:
type T1 is new I1 and I2 and I3 with [limited] private/record/null record;
****************************************************************
From: Robert A. Duff
Sent: Friday, October 29, 2004 9:33 AM
> I hate to have user-defined tag placement derail interesting
> ideas, unless it is really an important capability. Do you
> have any idea how often and for what purpose it is used?
Is there an issue with interface to C++?
Pascal wrote:
> Not all OO languages are like that (think of C++). I guess I have never
> seen this approach as very elegant language design.
I find it quite elegant, for what that's worth. I find the Ada/C++
approach, where there's no way to talk about the "root of everything" to
be the kludgy one. I doubt if I can convince you on a matter of taste
like this, but let me try this analogy: we have a "root of everything"
in the package hierarchy (package Standard). Do you find *that* to be
inelegant language design? How about the environment task, which is the
root of the task hierarchy? In general, it seems to me that hierarchies
should have roots.
>...We could even
> allow repeating "limited" if the first ancestor is a noninterface limited
> type, just for documentation.
I don't much like giving programmers such choices. If a programmer gets
used to seeing "limited" where it's optional, they will be confused when
they run across code written in the other style. Whatever is not
forbidden should be required. ;-) (And Jean Ichbiah should have decided
whether 'in' should be explicit or implicit in parameter decls, rather
than leaving it up to programmers.)
****************************************************************
From: Robert A. Duff
Sent: Friday, October 29, 2004 9:42 AM
> type T1 is new I1 and I2 and I3 with [limited] private/record/null record;
That syntax seems odd to me. You're not adding a limited extension.
You're saying that the whole thing is limited. I prefer Pascal's syntax
if we go that way.
However, I still prefer making it implicitly limited, because that's how
the old-style tagged types work (they inherit limitedness). So I think
this new syntax (either yours or Pascal's) will add confusion. So to
make it nonlimited, you would have to inherit from Root_Nonlimited, as
you suggested at first.
And I still think having everything derived from Root_Task and
Root_Limited and so forth is a good idea -- if we can figure out
how to make it work (re: tag placement).
****************************************************************
From: Jean-Pierre Rosen
Sent: Friday, October 29, 2004 10:04 AM
If you are polling opinions, I don't like the "big root" concept. A
principle of OO is that classes gather objects that have common
properties. "Object" gathers all objects that have no common properties.
A typical case where Robert would remind you that "to confuse" has
two meanings...
****************************************************************
From: Tucker Taft
Sent: Friday, October 29, 2004 10:25 AM
> If you are polling opinions, I don't like the "big root" concept. A
> principle of OO is that classes gather objects that have common
> properties. "Object" gathers all objects that have no common properties.
In this case, Limited_Root are the types that have tags.
Perhaps it would be better called "Tagged_Root."
All the other proposed "roots" clearly indicate common properties.
****************************************************************
From: Pascal Leroy
Sent: Friday, October 29, 2004 10:21 AM
> I find it quite elegant, for what that's worth. I find the
> Ada/C++ approach, where there's no way to talk about the
> "root of everything" to be the kludgy one. I doubt if I can
> convince you on a matter of taste like this, but let me try
> this analogy: we have a "root of everything"
> in the package hierarchy (package Standard). Do you find
> *that* to be inelegant language design? How about the
> environment task, which is the root of the task hierarchy?
> In general, it seems to me that hierarchies should have roots.
Actually, I would be happier if there was no package Standard, and if all
types were user-defined. This is not quite feasible with Boolean and
Character, but I certainly think that having predefined integer and float
types was a mistake.
The "root of everything" approach reminds me of Smalltalk's "everything is
an object" mantra. Sorry, I could never believe that the best
representation of an integer was an object to whom you would send the
"addition" message. Similarly the hierarchies of C++/Java/etc. force the
notion that things like streaming, hashing, etc. are relevant to all
objects, which is generally false (in a typical application, you only
stream or hash a minority of the types).
As you said, this is a matter of taste in the end. Consider this,
however: Smalltalk, Java and C++ do not have entire communities that make
heavy use of the language without ever using a tagged type or an
interface. I don't think the real-time/safety-critical community would be
very happy to get interfaces in every task or protected object.
****************************************************************
From: Pascal Leroy
Sent: Friday, October 29, 2004 10:27 AM
> However, I still prefer making it implicitly limited, because
> that's how the old-style tagged types work (they inherit
> limitedness). So I think this new syntax (either yours or
> Pascal's) will add confusion. So to make it nonlimited, you
> would have to inherit from Root_Nonlimited, as you suggested at first.
It seems to me that in real life most interfaces will be limited (as a
matter of fact I cannot think of many interfaces that would only make
sense in the presence of assignment) and many concrete types will be
nonlimited (replace many by most if we don't do a good job of fixing
limited types).
You are asking me to add "Ada.Roots.Root_Nonlimited and" to most type
declarations that make use of interface. So much for readability! And of
course, so much for the notion that simple things should be simple,
complex things should be possible.
****************************************************************
From: Steve Michell
Sent: Friday, October 29, 2004 11:29 AM
> As you said, this is a matter of taste in the end. Consider this,
> however: Smalltalk, Java and C++ do not have entire communities that make
> heavy use of the language without ever using a tagged type or an
> interface. I don't think the real-time/safety-critical community would be
> very happy to get interfaces in every task or protected object.
>
I think that this is a red herring Pascal. Tasks and Protected Objects already
clearly have a common root which contains the Task_Id, priority, runtime state
for tasks and some kind of ID, ceiling priority, lock and queues for a PO.
Creating a common root to support interfaces does nothing new.
On the other hand, to be acceptable to these communities, interfaces must be a
compile-only characteristic with no runtime effects.
****************************************************************
From: Randy Brukardt
Sent: Friday, October 29, 2004 12:50 PM
But the entire point of making things tagged is that it has a runtime
effect. That is, it has a tag that can be queried and the like. And it would
be necessary to be able to upconvert from Task_Root'Class to some specific
type.
Moreover, implementations could define Task_Root'Class operations. (Users
couldn't usefully do so, because they would have no access to any
commonality.)
So there would be a small runtime effect. One could argue that to the only
cost if you didn't use Task_Root'Class would be the added tag (and I suppose
Ravenscar would prohibit using Task_Root'Class or tag operations on a task).
Of course, the problem in my mind is that there are no predefined operations
for any of these interfaces. That means that nothing useful can be done with
them; to do anything, you'd have to test for membership and upconvert. And
if you really needed to do that, you could simply define your own interface
for the task.
So, in the absense of some operations for these types, it doesn't seem worth
it to have them. (And I don't think not doing it now would prevent us from
doing it in the future.)
****************************************************************
From: Tucker Taft
Sent: Friday, October 29, 2004 1:37 PM
> But the entire point of making things tagged is that it has a runtime
> effect. That is, it has a tag that can be queried and the like. And it would
> be necessary to be able to upconvert from Task_Root'Class to some specific
> type.
In all implementations I know of, every task and protected object
has a pointer to some kind of type descriptor. The issue is
whether this pointer can be made to look like a "tag". There
would be some nice advantages. For example, you could use
the operations of Ada.Tags to get an External_Name for the
task/protected type. And you could manage sets of tasks of
any task type with polymorphic lists using Task_Root'Class,
even if the task wasn't defined explicitly to use a task interface.
I suspect if we *don't* define a Task_Root type, then various
scheduling packages will appear that define their own Task_Root-like
interface, and then we will get into the trouble we have had
with proliferating String_Access types, etc. Every package
defines its own Task_Root-like thing, so you may end up having
battling task-roots, or having to define every task type as
being derived from multiple task-root-like things.
> Moreover, implementations could define Task_Root'Class operations. (Users
> couldn't usefully do so, because they would have no access to any
> commonality.)
Why do you say there woudn't be useful functionaliy? There are
attributes ('Callable and 'Terminated and 'Identity) and the
abort statement all of which can be applied to any task-interface
object as well.
> So there would be a small runtime effect. One could argue that to the only
> cost if you didn't use Task_Root'Class would be the added tag (and I suppose
> Ravenscar would prohibit using Task_Root'Class or tag operations on a task).
I don't think you need to add a tag, you just need to make sure that
your existing task/protected type descriptors are usable as tags.
> Of course, the problem in my mind is that there are no predefined operations
> for any of these interfaces. That means that nothing useful can be done with
> them; to do anything, you'd have to test for membership and upconvert. And
> if you really needed to do that, you could simply define your own interface
> for the task.
>
> So, in the absense of some operations for these types, it doesn't seem worth
> it to have them. (And I don't think not doing it now would prevent us from
> doing it in the future.)
Tasks have several language-defined operations, and the point here
is supporting natural extensibility in the future.
****************************************************************
From: Tucker Taft
Sent: Friday, October 29, 2004 1:43 PM
I would think we might agree that from a safety point of
view, encouraging users to write packages that take
Task_Root'Class rather than task-ids is better, since
you don't have the dangling references implicit in the
use of task ids.
****************************************************************
From: Randy Brukardt
Sent: Friday, October 29, 2004 2:18 PM
> In all implementations I know of, every task and protected object
> has a pointer to some kind of type descriptor. The issue is
> whether this pointer can be made to look like a "tag".
I doubt it very much. A tag is a read-only list of items, while a TCB is a
dynamic data structure. They're not even in the same logical address space
(mixing things that change with things that cannot change is what makes most
of the exploits used today possible). Certainly, it couldn't be done for
Janus/Ada.
Moreover, it makes no sense to rearrange and rewrite your entire task
supervisor in order to save the cost of a single tag in the task object! I
could imagine storing the tag *in* the TCB, but certainly not making the TCB
directly into the tag.
And in any case, the (small) cost of storing the tag somewhere is not going
away; it might be in the task object, or in the TCB, or wherever, but it
certainly isn't going to be free.
> There would be some nice advantages. For example, you could use
> the operations of Ada.Tags to get an External_Name for the
> task/protected type. And you could manage sets of tasks of
> any task type with polymorphic lists using Task_Root'Class,
> even if the task wasn't defined explicitly to use a task interface.
Yes, but you can't do anything useful with those lists.
> I suspect if we *don't* define a Task_Root type, then various
> scheduling packages will appear that define their own Task_Root-like
> interface, and then we will get into the trouble we have had
> with proliferating String_Access types, etc. Every package
> defines its own Task_Root-like thing, so you may end up having
> battling task-roots, or having to define every task type as
> being derived from multiple task-root-like things.
Yes, because those interfaces will have operations with them that will
provide some reason for doing this. There is no reason to do this without
operations.
> > Moreover, implementations could define Task_Root'Class operations. (Users
> > couldn't usefully do so, because they would have no access to any
> > commonality.)
>
> Why do you say there wouldn't be useful functionality? There are
> attributes ('Callable and 'Terminated and 'Identity) and the
> abort statement all of which can be applied to any task-interface
> object as well.
You can do those things on any task now, so I don't see any gain. And in any
case, none of those things are useful anyway. Who cares if a task is
callable if you have no entries to call? About the only thing you could do
would be to abort a list of tasks; that's not a large enough gain for making
a significant last-minute change.
> > Of course, the problem in my mind is that there are no predefined operations
> > for any of these interfaces. That means that nothing useful can be done with
> > them; to do anything, you'd have to test for membership and upconvert. And
> > if you really needed to do that, you could simply define your own interface
> > for the task.
> >
> > So, in the absence of some operations for these types, it doesn't seem worth
> > it to have them. (And I don't think not doing it now would prevent us from
> > doing it in the future.)
>
> Tasks have several language-defined operations, and the point here
> is supporting natural extensibility in the future.
The language-defined operations are useless (especially if you have a "no
abort" policy in place, which many projects do), and can be done anyway.
There is nothing incrementally added by this proposal that could be possibly
worthwhile.
I'd rather see this done right, with useful (common) operations defined on
all of the interfaces. And doing that is a big and likely controversial job.
An interface without operations is useless, because you can't do anything
with it.
****************************************************************
From: Tucker Taft
Sent: Friday, October 29, 2004 2:43 PM
The TCB and the task type descriptor are two different things.
But you are right, most task objects point at a TCB first and
foremost. However, either the task object or the TCB sometimes
also has a pointer to a *type* descriptor (the TCB is per-object,
the type descriptor is per-type). The type descriptor might
have the start address of the task body and other information
that characterizes the task type as opposed to the task object.
Having a type descriptor is probably more common for a protected
type, where the address of the various entry bodies might be
stored.
****************************************************************
From: Robert I. Eachus
Sent: Friday, October 29, 2004 6:20 PM
>I think that this is a red herring Pascal. Tasks and Protected Objects already clearly
>have a common root which contains the Task_Id, priority, runtime state for tasks and some
>kind of ID, ceiling priority, lock and queues for a PO. Creating a common root to support
>interfaces does nothing new.
Good point. I think it may be a little late for this big a change in
the language. It would certainly clarify/simplify some of the tasking
packages and features to make the Root_Task (or Task_Root) an explicit type.
But I think that in this case it is awful late for such a change in the
language, and even later for the more general root types.
****************************************************************