!standard 3.09.01 (03) 04-11-11 AI95-00345/07 !standard 2.09 (02) !standard 3.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 out 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 timed, conditional, and triggering 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 3.9(2) as: A record type or private type that has the reserved word tagged in its declaration is called a tagged type. {In addition, an interface type is a tagged type (see 3.9.4).} Redundant:[When deriving from a tagged type, [additional components may be defined. A]{a}s for any derived type, additional primitive subprograms may be defined, and inherited primitive subprograms may be overridden.] The derived type is called an extension of [the] {its} ancestor type{(s)}, or simply a type extension. Every type extension is also a tagged type, and is either a record extension {of some other tagged type,}[ or] a private extension{, or a task or protected type derived from an interface type (a synchronized tagged type -- see 3.9.4)} [of some other tagged type]. A record extension is defined by a derived_type_definition with a record_extension_part{ (see 3.9.1), which may include the definition of additional components}. A private extension, which is a partial view of a record extension {or of a synchronized tagged type}, can be declared in the visible part of a package (see 7.3) or in a generic formal part (see 12.5.1). 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 tagged 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 that provides a restricted form of multiple inheritance. A tagged {, task, or protected} type may have [multiple] {one or more} interface types as ancestors. 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 timed, conditional, and triggering entry calls. Hence, we now permit within a those entry calls 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(2) @drepl A record type or private type that has the reserved word @b in its declaration is called a @i. When deriving from a tagged type, additional components may be defined. As for any derived type, additional primitive subprograms may be defined, and inherited primitive subprograms may be overridden. The derived type is called an extension of the ancestor type, or simply a type extension. Every type extension is also a tagged type, and is either a record extension or a private extension of some other tagged type. A record extension is defined by a @fa with a @fa. A private extension, which is a partial view of a record extension, can be declared in the visible part of a package (see 7.3) or in a generic formal part (see 12.5.1). @dby A record type or private type that has the reserved word @b in its declaration is called a @i. In addition, an interface type is a tagged type (see 3.9.4). When deriving from a tagged type, as for any derived type, additional primitive subprograms may be defined, and inherited primitive subprograms may be overridden. The derived type is called an extension of its ancestor type(s), or simply a type extension. Every type extension is also a tagged type, and is either a record extension of some other tagged type, a private extension, or a task or protected type derived from an interface type (a synchronized tagged type @emdash see 3.9.4). A record extension is defined by a @fa with a @fa (see 3.9.1), which may include the definition of additional components. A private extension, which is a partial view of a record extension or of a synchronized tagged type, can be declared in the visible part of a package (see 7.3) or in a generic formal part (see 12.5.1). !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 tagged 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<@s8> 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 that provides a restricted form of multiple inheritance. A tagged, task, or protected type may have one or more interface types as ancestors. !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<@fa@ft<@b>@fa< defining_identifier [known_discriminant_part] [>@ft<@b>@fa< task_definition];>> @dby @xcode<@fa@ft<@b>@fa< defining_identifier [known_discriminant_part] [>@ft<@b>@fa< [>@ft<@b>@fa< interface_list >@ft<@b>@fa<] task_definition];>> !corrigendum 9.1(8) @ddel @i<@s8> 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<@s8> 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<@fa@ft<@b>@fa< defining_identifier [known_discriminant_part] [>@ft<@b>@fa< protected_definition];>> @dby @xcode<@fa@ft<@b>@fa< defining_identifier [known_discriminant_part] [>@ft<@b>@fa< [>@ft<@b>@fa< interface_list >@ft<@b>@fa<] protected_definition];>> !corrigendum 9.4(10) @ddel @i<@s8> 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<@s8> 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<@fa> @dby @xcode<@fa> @xcode<@fa> @i<@s8> 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<@s8> 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<@fa> @dby @xcode<@fa> !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. ****************************************************************