!standard 3.04.03 (00) 03-01-22 AI95-00345/03 !class amendment 03-08-07 !status work item 03-09-28 !status received 03-06-12 !priority Medium !difficulty Hard !subject Protected and task interfaces !summary Protected and Task interfaces are proposed. A protected or task type may specify one or more interfaces as ancestors. The synchronizing operations (entries and protected subprograms) of these interfaces are inherited by the protected or task type. If the operations are declared abstract in the interface, they must be overridden in the inheriting type. To integrate this feature with tagged interface types, we allow task and protected types/interfaces to be derived from "normal" limited interfaces. (This added capability is not essential, and could be dropped if it overburdens the proposal.) !problem The object-oriented features of Ada 95 are essentially disjoint with the multi-tasking features of Ada 95. This means that it is difficult to combine synchronization with type extension and polymorphism. Although there are some approaches to doing so using access discriminants, they tend to be a bit awkward, and they don't actually prevent unsynchronized access to the object designated by the access discriminant. !proposal (See wording.) !wording NOTE: This presumes AI-251 ("normal" interface types). We will identify where we are referring to AI-251 wording. Modify 3.4(4): Class-wide types Class-wide types are defined for (and belong to) each derivation class rooted at a tagged {or interface} type (see 3.9 {and 3.9.4}) ... Modify 3.9(13): For every subtype S of a tagged {or interface} type T (specific or class-wide), the following attribute is defined: Replace 3.9.3(1-2) with: An abstract type is a type intended for use as an ancestor of other types, but which is not allowed to have objects of its own. Static semantics Interface types (see 3.9.4) are abstract types. In addition, a tagged type that has the reserved word abstract in its declaration is an abstract type. The class-wide type (see 3.4.1) rooted at an abstract type is not itself an abstract type. Legality Rules Only a tagged type shall have the reserved word abstract in its declaration. In the wording of AI-251: Modify 3.9.4(1): An interface type is an abstract [tagged] type intended for use in providing a restricted form of multiple inheritance. A tagged type may be derived from multiple interface types. {A task or protected type may be derived from multiple limited interface types. Limited interface types that are specifically for use in defining task or protected types may also be defined (see 9.1 and 9.4).} Modify 3.9.4(3): An interface type (also called an "interface") {defined by an interface_type_definition} is a specific abstract tagged type [that is defined by an interface_type_definition]. [end of AI-251-relative modifications] Add after 6.3.1(24): Two subprograms or entries are type conformant (respectively mode conformant, subtype conformant, or fully conformant) if their profiles are type conformant (respectively mode conformant, subtype conformant, or fully conformant). Two entry families are subtype conformant if their profiles are subtype conformant and their entry index subtypes match statically. In the wording of AI-251: Modify 8.3(9/1): ... The only declarations that are overridable are the implicit declarations for predefined operators[ and]{,} inherited primitive subprograms{, inherited abstract entries and entry families, and inherited abstract or null protected subprograms}. ... Modify 8.3(in part of replacement for para 13): - If all are null procedures or abstract subprograms{ (possibly protected), entries, or entry families}, then any null procedure overrides all abstract subprograms; if more than one homograph remains that is not thus overridden, then one is chosen arbitrarily to override the others. Modify 8.3(26/1 part 2): If two or more homographs are implicitly declared at the same place {and there is no non-overridable declaration that overrides them} then at most one shall be a non-null non-abstract subprogram. If all are null or abstract {subprograms, entries, or entry families}, then all of the null subprograms shall be fully conformant with one another. If all are abstract, then all of the subprograms{, entries, or entry families} shall be fully conformant with one another. [end of AI-251-relative modifications] Add to the end of 9.1(1): A task_interface_declaration declares a task interface type, specifying entries or entry families that shall be provided by any task type derived from the interface. Change 9.1(2) to: task_type_declaration ::= TASK TYPE defining_identifier [known_discriminant_part] [IS [NEW interface_list WITH] task_definition]; Add after 9.1(5/1): task_interface_declaration ::= TASK INTERFACE defining_identifier IS [NEW interface_list WITH] task_interface_definition; task_interface_definition ::= {abstract_entry_declaration} END [task_identifier] Change 9.1(7) to: If a task_identifier appears at the end of a task_definition{, task_interface_definition,} or task_body, it shall repeat the defining_identifier. Add after 9.1(8): Each interface_subtype_mark of an interface_list appearing within a task_type_declaration or a task_interface_declaration shall denote a limited interface type -- it may be a task interface type; it shall not be a protected interface type. If a task_type_declaration includes an interface_list, then each abstract entry or entry family inherited by the task type shall be overridden with a subtype conformant entry_declaration within the task_type_declaration. If a task_interface_declaration includes an interface_list and an abstract_entry_declaration of the task_interface_definition overrides an inherited entry or entry family, the explicitly declared entry or entry family shall be subtype conformant with the inherited entry or entry family. Add after 9.1(9.1/1): A task_interface_definition defines a task interface type and its first subtype. A task interface type is a limited interface type (see 3.9.4). The list of abstract_entry_declarations is the visible part of the task_interface_declaration. If a task_type_declaration includes an interface_list, the task type is derived from each interface named in the interface_list. Similarly, if a task_interface_declaration includes an interface_list, the task interface type is derived from each interface named in the interface_list. Add after 9.1(12/1): The elaboration of a task_interface_declaration elaborates the task_interface_definition. The elaboration of a task_interface_definition creates the task interface type and its first subtype; it also includes the elaboration of the abstract_entry_declarations in the given order. Add to the end of 9.4(1): A protected_interface_declaration declares a protected interface type, specifying protected operations that are to be available for any protected type derived from the interface. Change 9.4(2) to: protected_type_declaration ::= PROTECTED TYPE defining_identifier [known_discriminant_part] IS [NEW protected_interface_name {AND protected_interface_name} WITH] protected_definition; Change 9.4(5/1) to: protected_operation_declaration ::= subprogram_declaration | null_procedure_declaration | entry_declaration | aspect_clause Add after 9.4(6): protected_interface_declaration ::= PROTECTED INTERFACE defining_identifier IS [NEW limited_interface_subtype_mark {AND protected_interface_name} WITH] protected_interface_definition; protected_interface_definition ::= {protected_interface_item} END [protected_identifier] protected_interface_item ::= abstract_subprogram_declaration | null_procedure_declaration | abstract_entry_declaration Change 9.4(9) to: If a protected_identifier appears at the end of a protected_definition{, protected_interface_definition,} or protected_body, it shall repeat the defining_identifier. Add after 9.4(10): Each interface_subtype_mark of an interface_list appearing within a protected_type_declaration or a protected_interface_declaration shall denote a limited interface type -- it may be a protected interface type; it shall not be a task interface type. If a protected_type_declaration includes an interface_list, then each abstract entry, abstract entry family, and abstract protected subprogram inherited by the protected type shall be overridden with a subtype conformant protected_operation_declaration within the protected_type_declaration. If a protected_interface_declaration includes an interface_list and a protected_interface_item of the protected_interface_definition overrides an inherited entry, entry family, or protected subprogram, the explicitly declared entity shall be subtype conformant with the inherited entity. Modify 9.4(11): A protected_interface_definition defines a protected interface type and its first subtype. A protected interface type is a limited interface type (see 3.9.4). The list of protected_interface_items is the visible part of the protected_interface_declaration. If a protected_type_declaration includes an interface_list, the protected type is derived from each interface named in the interface_list. Similarly, if a protected_interface_declaration includes an interface_list, the protected interface type is derived from each interface named in the interface_list. Add after 9.4(13): The elaboration of a protected_interface_declaration elaborates the protected_interface_definition. The elaboration of a protected_interface_definition creates the protected interface type and its first subtype; it also includes the elaboration of the protected_interface_items in the given order. Add after 9.5.2(2): abstract_entry_declaration ::= ENTRY defining_identifier [(discrete_subtype_definition)] parameter_profile IS ABSTRACT; Modify 9.5.2(13): An entry_declaration in a task declaration {or an abstract_entry_declaration in a task_interface_declaration} shall not contain a specification for an access parameter (see 3.10). Modify 9.5.2(20): An entry_declaration {or abstract_entry_declaration} with a ... Modify 9.5.2(22/1): The elaboration of an entry_declaration {or abstract_entry_declaration} for an entry family ... The elaboration of an entry_declaration {or abstract_entry_declaration} for a single entry has no effect. !example protected interface Queue is -- Interface for a protected queue entry Enqueue(Elem : in Element_Type) is abstract; entry Dequeue(Elem : out Element_Type) is abstract; function Length return Natural is abstract; end Queue; type Queue_Ref is access all Queue'Class; protected type Bounded_Queue(Max: Natural) is new Queue with -- Implementation of a bounded, protectected queue entry Enqueue(Elem : in Element_Type); entry Dequeue(Elem : out Element_Type); function Length return Natural; private Data: Elem_Array(1..Max); In_Index: Positive := 1; Out_Index: Positive := 1; Num_Elems: Natural := 0; end My_Queue; task interface Worker is -- Interface for a worker task entry Queue_To_Service(Q : Queue_Ref) is abstract; end Server; type Worker_Ref is access all Worker'Class; task type Cyclic_Worker is new Worker with -- Implementation of a cyclic worker task entry Queue_To_Service(Q : Queue_Ref); end Cyclic_Server; task Worker_Manager is -- Task that manages servers and queues. entry Add_Worker_Task(W : Worker_Ref); entry Add_Queue_To_Be_Serviced(Q : Queue_Ref); end Worker_Manager; task body Worker_Manager is Worker_Array : array(1..100) of Worker_Ref; Queue_Array : array(1..10) of Queue_Ref; Num_Workers : Natural := 0; Next_Worker : Integer := Worker_Array'First; Num_Queues : Natural := 0; Next_Queue : Integer := Queue_Array'First; begin loop select accept Add_Worker_Task(W : Worker_Ref) do Num_Workers := Num_Workers + 1; Worker_Array(Num_Workers) := Worker_Ref(W); end Add_Worker_Task; -- Assign new task a queue to service if Num_Queues > 0 then -- Assign next queue to this worker Worker_Array(Num_Workers).Assign_Queue_To_Service( Queue_Array(Next_Queue)); -- Dynamically bound entry call -- Advance to next queue Next_Queue := Next_Queue mod Num_Queues + 1; end if; or accept Add_Queue_To_Be_Serviced(Q : Queue_Ref); Num_Queues := Num_Queues + 1; Queue_Array(Num_Queues) := Queue_Ref(Q); end Add_Queue_To_Be_Serviced; -- Assign queue to worker if enough workers if Num_Workers >= Num_Queues then -- This queue should be given one or more workers declare Offset : Natural := Num_Queues-1; begin while Offset < Num_Workers loop -- (re) assign queue to worker Worker_Array((Next_Worker + Offset - Num_Queues) mod Num_Workers + 1). Assign_Queue_To_Service(Queue_Array(Num_Queues)); -- Dynamically bound entry call Offset := Offset + Num_Queues; end loop; -- Advance to next worker Next_Worker := Next_Worker mod Num_Workers + 1; end; end if; or terminate; end select; end loop; end Worker_Manager; My_Queue : aliased Bounded_Queue(Max => 10); My_Server : aliased Cyclic_Server; begin Worker_Manager.Add_Worker_Task(My_Server'Access); Worker_Manager.Add_Queue_To_Be_Serviced(My_Queue'Access); ... !discussion During the Ada 95 design process, it was recognized that type extension might be useful for protected types (and possibly task types) as well as for record types. However, at the time, both type extension and protected types were somewhat controversial, and expending energy on a combination of these two controversial features was not practical. Since the design, however, this lack of extension of protected types has been identified as a possible target for future enhancements. In particular, a concrete proposal appeared in the May 2000 issue of ACM Transactions on Programming Languages in Systems (ACM TOPLAS), and this has formed the basis for a language amendment (AI-00250). However, in ARG discussions, the complexity of this proposal has been of concern, and more recently a simpler suggestion was made that rather than supporting any kind of implementation inheritance, interfaces for tasks and protected types might be defined, and then concrete implementations of these interfaces could be provided. Class-wide types for these interfaces would be defined, and calls on the operations (protected subprograms and entries) defined for these interfaces could be performed given only a class-wide reference to the task or protected object. An important advantage of eliminating inheritance of any code or data for tasks and protected types is that the "monitor"-like benefits of these constructs are preserved. All of the synchronizing operations are implemented in a single module, simplifying analysis and avoiding any inheritance "anomolies" that have been associated in the literature with combining inheritance with synchronization. As with "normal" interfaces, a "concrete" type that inherits from an interface must override all abstract operations, but may inherit null procedures. We have not proposed null entries, since it is unclear what would be the barriers for those (True or False) or how they would fit into a task body (no accept statement?). Objects of type 'Class can be used as the prefix in a call on a synchronizing operation, and a run-time dispatch would occur to the "appropriate" entry/protected subprogram. Question: Should we allow declaration of operations outside the protected/task_interface definition that take directly a a protected or task interface, as opposed to the corresponding class-wide type? Suggested answer: Initially we suggested no, but Pascal pointed out that our original proposal didn't really integrate inheritance between tagged types and task/protected types. However, we now allow "normal" limited interface types as ancestors of task and protected types or interfaces. This implies that we will have to support dispatching calls through lim-interface'class for such task and protected types, so we might as well allow task and protected interfaces to have primitive subprograms as well. Note that this capability is clearly separable from the "dispatching" inherent in invoking entries and protected subprograms, and could be dropped from the proposal if it is felt to overburden it. --!corrigendum 03.0x.0x(0x) !ACATS test !appendix From: Randy Brukardt Sent: Thursday, June 12, 2003 7:43 PM Tucker wrote me: Here is an article I have submitted to the Ada User Journal. It might be of interest to ARG members. Rather than filling all of their mailboxes with it, I thought I would just fill yours ;-). Once you get it posted, could you send out an e-mail with a URL pointing to it? --- The article is posted at http://www.ada-auth.org/ai-files/grab_bag/oop_200y.pdf. Happy reading! [Editor's note: This article proposes protected interfaces.] **************************************************************** From: Robert I. Eachus Sent: Thursday, June 12, 2003 11:19 PM I was interested to compare the three cyclic type structure proposals as they appear in Tuck's examples. They all occupy about the same volume in number of lines, but the generalized incomplete type approach definitely looks the cleanest/most Ada-like. There are of course, plenty of reasons why any one of the three proposals might turn out to be problematical for other reasons, but I think this causes me to lean a little more in the direction I was leaning anyway. Oh, and the write-up of protected interfaces seems to me to be the best argument for adding interfaces to the language. I'll have to think about it (a lot). I like the idea of having all queue types match a potentially tasking safe interface. Some implementations could be tasking safe and others assume they are only visible in a single thread. A wonderful extension to Ada as an expository language for algorithm design. Now all we have to do is figure out how to actually implement it. ;-) **************************************************************** From: Pascal Leroy Sent: Friday, June 13, 2003 11:03 AM Is this really what Tucker is proposing? The way I read his paper, protected and non-protected interfaces are distinct beasts and do not mix. So if a queue has a protected interface, all of its implementations have to be protected. I might be misreading the intent of course, it's hard to know without an AI. But because the calling conventions of protected and non-protected operations are vastly different, I don't see how a class-wide type could indifferently designate a protected or a non-protected implementation. **************************************************************** From: Tucker Taft Sent: Friday, June 13, 2003 1:22 PM I was not proposing mixing protected interfaces and tagged interfaces. That would seem to be a bad idea, given that the semantics are so different. I'm not sure exactly what Robert Eachus had in mind, but if you want to mix protected and tagged, you will have to "wrap" one in the other. **************************************************************** From: Robert I. Eachus Sent: Friday, June 13, 2003 1:22 PM I don't think it is what Tucker is proposing, and a pragma would be useful. But Tucker's proposal certainly allows what I am thinking of. Imagine two implementations of a protected (class-wide) type. One version, say the parent is a "normal" protected object with all of the necessary baggage to support say a queue which is accessed by different threads/tasks. With Tucker's proposal you can also provide a child implementation which will fail in one of many ways, possibly by deadlock, if it is actually called by two different tasks. And maybe another child that works fine with at most two callers, but can livelock or deadlock with three or more. It would be nice to have a pragma that told the compiler that "this protected body assumes a single task," if only for documentation purposes. Even without it, the checks/overhead in the single thread version should be low. With a pragma, the compiler can choose a locking implementation which has very low overhead for passing through, and a much higher overhead--or even Program_Error--if a caller ever blocks. The interface is identical, and any compilers that do lock checking outside the protected object code don't need to change. Let me give an example. Suppose you have a protected object type that provides serialized access to a disk file, perhaps of a Sequential_IO file with a (Direct_IO) associated index. (Reading or writing would require several calls on the protected object.) The simple implementation would allow only one transaction in process at a time. (Begin_Transaction, Lookup, Read or Write, Close_Transaction.) The more complex implementation could allow for several transactions to be in progress at the same time. (File locking vs. record locking.) The key point is that this effectively adds a capability to Ada that has been there all along, but in general becomes too difficult to use. Right now you can customize generics, but that requires preplaning to provide the right generic formal parameters. A good example of this type of customization is providing different ">" operators as parameters to a sort routine allows the same sort routine to be used to sort on different keys. But allowing for full generality is just not possible. The original implementor of the generic has to think of all the possible customizations ahead of time, and provide the additional generic parameters to accomplish it. Does this sound something like the problem with variant records that tagged types solved? It does to me. Fortunately, I think that the hard work has already been done by existing compilers, what is needed is the (trivial?) bit of effort required to permit programmers to provide alternate bodies for generics. I'm working on writing this up. Basically think of it as adding derived generics to the language. The "new" generic has the same interface as its parent but provides a new body. It might be nice to allow the new body (assuming that the generic is a package) to call operations of the parent, but I think it is adequate to allow/require the programmer to create an instance of the parent generic if he wants to do that. **************************************************************** From: Alan Burns Sent: Monday, October 27, 2003 4:07 AM The last meeting of ARG asked for some further examples of the use of protected interfaces and task interfaces. In general there is no current way of giving more than one body to a PO or task. This would be useful even if no further components are added. Clients can link to an interface for a Server; actual Server can be one of many types. But also in a client server relation, it can be useful to ensure all server tasks provide a basic service: task type interface Basic_Server is entry basic server() is abstract; end Basic_Server; this could lead to many server components such as: task type interface Degraded_Server is new Basic_Server; or task type Fuller_Server is new Basic_Server with entry basic_service(); entry full service(); end Fuller_Server; For POs to ensure all POs have a mode change operation could be done with a suitable interface. Also to control visability over entries: The usual buffer: protected type Buffer is entry put(X: in Element); entry get(X : out Element); private ... end Buffer; Problem: Can't separate producer and consumer interface, but protected interface Producer is entry Put(X: in Element) is abstract; end producer; protected interface Consumer is entry Get(X : out Element) is abstract; end Consumer; then protected type Buffer is new Producer and Consumer with entry put(X: in Element); entry get(X : out Element); private ... end Buffer; or protected type ForwardingBuffer is new Producer and Consumer with entry put(X: in Element); entry get(X : out Element); private ... end Buffer; A further example comes from the use of a PO as a resource controller. Here the specification is often of the form: protected interface Resource_Controller is entry Get() is abstract; procedure Release() is abstract; private ... end Resource_Controller; This would allow many different implementations; also allow entries and procedures to be added (such as Release_All). **************************************************************** From: Tucker Taft Sent: Thursday, January 22, 2004 10:34 AM Here is the first write-up of this AI that includes "real" wording. In response to Pascal's concern that we weren't *really* integrating inheritance between tagged and task/protected types, I have modified this proposal to allow task/protected types to be derived from "normal" limited interfaces as well as from task/protected interfaces. I don't believe this imposes a significant added implementation burden, and clearly provides better integration of inheritance capabilities. However, it would be easy enough to "back out" this added capabiltiy, if it is felt to overburden the proposal. In my view it would also be nice to allow non-limited types to be derived from limited interfaces, but that depends on fixing the return-by-reference function morass, perhaps via the "aliased" return proposal. In any case, that idea is essentially unrelated to this AI, except in that it also provides further integration of inheritance capabilities. **************************************************************** From: Tucker Taft Sent: Thursday, January 22, 2004 10:34 AM Here is the first write-up of this AI that includes "real" wording. In response to Pascal's concern that we weren't *really* integrating inheritance between tagged and task/protected types, I have modified this proposal to allow task/protected types to be derived from "normal" limited interfaces as well as from task/protected interfaces. I don't believe this imposes a significant added implementation burden, and clearly provides better integration of inheritance capabilities. However, it would be easy enough to "back out" this added capabiltiy, if it is felt to overburden the proposal. In my view it would also be nice to allow non-limited types to be derived from limited interfaces, but that depends on fixing the return-by-reference function morass, perhaps via the "aliased" return proposal. In any case, that idea is essentially unrelated to this AI, except in that it also provides further integration of inheritance capabilities. [Editor's note: this is version /03.] **************************************************************** From: Robert I. Eachus Sent: Thursday, January 22, 2004 11:35 AM Tucker Taft wrote: > Here is the first write-up of this AI that includes "real" wording... I like it. I do have one nit to pick that is not a serious issue: > Legality Rules > > Only a tagged type shall have the reserved word abstract in its > declaration. A (hostile) reading of this rule is that abstract can only appear in (tagged) TYPE declarations. A clearer wording might be: "A type declaration that has the reserved word abstract in its definition shall be a tagged type." We could even go a little further and say: " ...a tagged view of a type." That makes it clear that private views must include tagged or with, and a derived abstract type must be derived from a tagged view. I admit I am being pedantic, but I think that is the proper way to read proposed wording. **************************************************************** From: Robert A. Duff Sent: Thursday, January 22, 2004 11:36 AM > "A type declaration that has the reserved word abstract in its > definition shall be a tagged type." Yes, that's better wording. Or just, "...shall be tagged". > We could even go a little further and say: " ...a tagged view of a > type." That makes it clear that private views must include tagged or > with, and a derived abstract type must be derived from a tagged view. I don't think we need to say "view". The RM contains many places where "type" must be interpreted to mean "view of a type". **************************************************************** From: Tucker Taft Sent: Thursday, January 22, 2004 11:47 AM > Robert Eachus said: > > > "A type declaration that has the reserved word abstract in its > > definition shall be a tagged type." > > Yes, that's better wording. Or just, "...shall be tagged". Well I think you need to say "shall be *for* a tagged type." A type declaration is not itself "tagged". In any case, I started from the admittedly somewhat awkward existing Ada 95 wording and tried to preserve the "spirit" of it: Only a tagged type is allowed to be declared abstract. became: Only a tagged type shall have the reserved word abstract in its declaration. I don't love it, but I'm not sure I really prefer the above proposed alternatives either. Any other opinions? > > We could even go a little further and say: " ...a tagged view of a > > type." That makes it clear that private views must include tagged or > > with, and a derived abstract type must be derived from a tagged view. > > I don't think we need to say "view". The RM contains many places where > "type" must be interpreted to mean "view of a type". I agree. **************************************************************** From: Robert I. Eachus Sent: Thursday, January 22, 2004 4:01 PM >I don't think we need to say "view". The RM contains many places where >"type" must be interpreted to mean "view of a type". I have no problem with that. And my proposed new wording was really a fishing expedition to find a better way to say it. One alternative is: a) "A type declaration that contains the reserved word abstract shall declare a tagged type." Of course, the temptation to replace "shall" by "must" must be resisted. ;-) b) A declaration of a non-tagged type is illegal if it contains the reserved word abstract." works and isn't too stilted. Better might be: c) "A type declaration that contains the reserved word abstract is illegal if it does not declare a tagged type." But althought the meaning is clear, it is technically nonsense. An illegal declaration doesn't declare anything. That brings us to: d) "A type declaration that contains the reserved word abstract is legal only if it declares a tagged type." Alternative d is shorter than c, and does a good job of making its intent clear. Can we go further and say: e) "An abstract type declaration shall declare a tagged type." That opens a can of worms, but I think it is one that is worth opening. Right now, the proposal says that task interface types and protected interface types are also abstract types. If we change the definitions around, we could have "abstract types", "task interface types", and "protected interface types" be exclusive. I could go through and make the other changes if people think it is worth it. As I see it, doing that would clean up a lot of awkward text, expecially in the proposed 3.9.4(1), and in the changes to 9.1 and 9.4. Abstract type does not seem to currently be heavily used in the RM, and in any case we would now not be changing the meaning of the technical term. What does get used is abstract subprogram. Adding abstract entries means that we should add that a call to an abstract entry must be dispatching. (Excuse me, shall be dispatching.) Tucker said: "In my view it would also be nice to allow non-limited types to be derived from limited interfaces, but that depends on fixing the return-by-reference function morass, perhaps via the "aliased" return proposal. In any case, that idea is essentially unrelated to this AI, except in that it also provides further integration of inheritance capabilities." If we do adopt the final alternative above, it might make it harder to do this. But I don't see any benefit to allowing non-tasks to inherit from task interfaces or non-protected types to inherit from protected interfaces (I also can't imagine syntax rules that would allow anything new other than protected types inheriting from a task interface, or vice versa.) So it does seem to be unrelated. **************************************************************** From: Tucker Taft Sent: Thursday, January 22, 2004 4:28 PM > Alternative d is shorter than c, and does a good job of making its > intent clear. Can we go further and say: > > e) "An abstract type declaration shall declare a tagged type." Now you are pretty much back to where I started with Ada 95. The whole point was to allow abstract types to include interfaces. I think this is a bug in the current wording of AI-251, by the way. > That opens a can of worms, but I think it is one that is worth opening. > Right now, the proposal says that task interface types and protected > interface types are also abstract types. If we change the definitions > around, we could have "abstract types", "task interface types", and > "protected interface types" be exclusive. Why would that be a good idea? An abstract type is one that is not allowed to have objects (aka "instances" in OO parlance). > ... I could go through and make > the other changes if people think it is worth it. As I see it, doing > that would clean up a lot of awkward text, expecially in the proposed > 3.9.4(1), and in the changes to 9.1 and 9.4. I don't agree at all. We didn't invent the notion of "abstract type." It is well established in the OO literature as a type that should not have any instances. That applies to all interface types, as well as all types explicitly declared "abstract." > Abstract type does not seem to currently be heavily used in the RM, and > in any case we would now not be changing the meaning of the technical > term. We would be changing the fundamental meaning of "abstract" type. > ... What does get used is abstract subprogram. Adding abstract > entries means that we should add that a call to an abstract entry must > be dispatching. (Excuse me, shall be dispatching.).. I'm not sure that is necessary. Entry queues are associated with objects, and calls are added to entry queues and then "serviced". I think that provides the implicit level of indirection that is equivalent to dispatching. On the other hand, we may need to say something more about protected subprogram calls when the prefix is class-wide. **************************************************************** From: Tucker Taft Sent: Thursday, January 22, 2004 12:02 PM Tucker Taft wrote: > ... I have modified this > proposal to allow task/protected types to be derived > from "normal" limited interfaces as well as from > task/protected interfaces. ... I suppose we could take one further step, and allow task types/interfaces to be derived from protected interfaces that have only abstract entry/entry families. Going the other way (deriving protected types/interfaces from task types) doesn't seem wise, because there are various operations you can apply to task_int'class which wouldn't make sense for protected objects (e.g. abort, 'identity). On the other hand, I don't know of any operations that can be applied to a protected object with only entries that couldn't also be applied to a task. Again, this is incremental functionality that is not essential to the basic proposal, and shouldn't be allowed to overburden it... **************************************************************** From: Randy Brukardt Sent: Thursday, January 22, 2004 7:16 PM Tucker Taft wrote: > > > ... I have modified this > > proposal to allow task/protected types to be derived > > from "normal" limited interfaces as well as from > > task/protected interfaces. ... > > I suppose we could take one further step, and allow > task types/interfaces to be derived from protected > interfaces that have only abstract entry/entry families. I don't know if that is useful. But the problem with this proposal is that it doesn't seem to help in all of the cases where you might want to mix implementations. For one thing, while a task can be derived from a "regular" limited interface, such an interface has to be devoid of operations -- making the capability fairly useless. For another thing, a lot of the capabilities that you might want don't seem to be possible. Consider a queue interface (I'm using fixed items here for simplicity; it's likely that this whole thing would be wrapped in a generic): package AQ is type Abstract_Queue is limited interface; procedure Add_Item (Queue : in out Abstract_Queue; Item : in Integer); procedure Remove_Item (Queue : in out Abstract_Queue; Item : out Integer); end AQ; Then the standard implementation would look something like: with AQ; package NQ is type Queue is new AQ.Abstract_Queue with private; procedure Add_Item (Queue : in out Queue; Item : in Integer); procedure Remove_Item (Queue : in out Queue; Item : out Integer); -- Raises Empty_Error if empty. Empty_Error : exception; end NQ; Of course, the interface could be used in other components that needed a queue. Now, say you want a version of a queue to use for multiple tasks, which blocks on empty rather than raising an exception. You'd write something like: with AQ; package PQ is protected type Blocking_Queue is new AQ.Abstract_Queue with procedure Add_Item (Item : in Integer); entry Remove_Item (Item : out Integer); private ... end Blocking_Queue; end PQ; But of course that isn't allowed, because Remove_Item isn't a procedure. Moreover, these aren't subtype conformant. Your wording fails to take into account the implied object of the calls. (Also, Remove_Item can't be a function, because of some stupid rule about "in out" parameters on functions. I've made it a procedure here for that reason - Ada functions are useless for O-O programming other than possibly constructors. But's that's another argument. Note that the usual work-arounds aren't going to work for a protected type, because you can't change the implied parameter's mode or handling.) with AQ; package PQ1 is protected type Blocking_Queue is new AQ.Abstract_Queue with procedure Add_Item (Item : in Integer); procedure Remove_Item (Item : out Integer); entry Blocking_Remove_Item (Item : out Integer); private ... end Blocking_Queue; end PQ1; Doesn't work either, because Remove_Item can't call Blocking_Remove_Item (can't call blocking operations in a protected object). A Remove_Item that raised an exception would work, but it would defeat the purpose of having the operation. So, you'd have to resort to a wrapper type (assuming the protected type in PQ above exists): with AQ, PQ; package PQ2 is type Blocking_Queue is new AQ.Abstract_Queue with record Real_Queue : PQ.Blocking_Queue; end record; procedure Add_Item (Queue : in out Blocking_Queue; Item : in Integer); procedure Remove_Item (Queue : in out Blocking_Queue; Item : out Integer); end PQ2; Now you can use your protected queue object in the other components. But now, it is no longer protected, unless you make the component itself visible (as I did above). So callers that want to use (say) a timed entry call would have to call the component of the queue, not an operation on the queue. That's pretty ugly. Moreover, if you're willing to write wrappers, you don't need the ability to use "regular" interfaces on protected types in the first place. Just write the wrapper and be done with it. The beauty of this idea is to get the compiler to write any needed wrappers. (Or generate its code so it doesn't need any.) Thus, I think that an entry should be able to "match" a procedure with the proper parameter list (and no family). (This would essentially be the prefix call mechanism in reverse.) The call would operate like a procedure call. We already allow this in Ada, as an entry can be renamed as a procedure. Allowing that would make the above example easy, and also would make a "regular" interface useful for a task. Without it, I don't see much point in the whole mechanism. Other than basic locks (which you should avoid anyway), there don't seem to be that many cases of wanting multiple implementations of protected objects or tasks. ****************************************************************