!standard 3.9.2(10/2) 18-01-11 AI12-0243-1/01 !class Amendment 18-01-11 !status work item 18-01-11 !status received 17-10-20 !priority Very_Low !difficulty Medium !subject Subtypes as primitive arguments !summary ** TBD. !problem The use of subtypes with dynamic predicates can be used to factor out common checks from the preconditions of an interface. This reduces code duplication and reduces the chance of forgetting a check. The technique is important enough that an example was provided in the Standard, in the examples for subclause 3.2.4. It shows a hypothetical update to Text_IO using subtypes with dynamic predicates to provide most of the precondition checks needed. However, if the type in question is tagged, Ada does not allow controlling operands to have non-first subtypes. This means that the technique cannot be used on the primitive operations of a tagged type. !proposal (See Summary.) !wording ** TBD. !discussion Tucker sent the following explanation of the restriction: The "technical reasons" relate to the need to perform constraint checks at a call site. This is true for dispatching operations as well -- the checks are performed *before* dispatching to the chosen body. If we start having dispatching operations with non-first-subtypes for parameter subtypes, then you have to be sure that the parameters subtypes in an overriding of a dispatching routine have exactly the same checks required at the point of call. This would imply defining statically matching subtypes of different types, which seemed more of a challenge than we were prepared for when Ada 95 was designed. Now that we have pre/postconditions, etc., we have accepted that wrappers may be necessary in any case. If we are willing to require wrappers due to parameter subtype mismatches, we could probably do that, but it could pose further efficiency challenges for implementations to avoid wrappers when not necessary. ------ In some cases, one can use a class-wide type to work-around the restriction. Also, one can write separate preconditions for every primitive subprogram, including a membership in the dynamic predicate(s), rather than using the more convinient parameter subtype(s). !ASIS None needed. !ACATS test An ACATS C-Test is needed to check that the new capabilities are supported. !appendix !topic Subtypes as primitive arguments !reference Ada 2012 RM !from Victor Porton 17-10-21 !keywords subtype subprogram argument primitive subprogram !discussion I propose to allow subtypes (for example subtypes with predicates) as controlling primitive subprogram arguments. A subtype ST of type T for argument A should work as the base type T, but with checking if "T(A) in ST" before doing dispatch (and raising an exception otherwise). This is probably especially useful with predicates. Now as a workaround I replace ST with ST'Class in my code. **************************************************************** From: Tucker Taft Sent: Friday, October 20, 2017 4:28 PM There are some technical reasons why dispatching operations were restricted to having "first" subtypes for their controlling operands. Can you give some concrete examples where using (non-first) subtypes, perhaps with predicates, would be necessary to implement a given abstraction? **************************************************************** From: Randy Brukardt Sent: Friday, October 20, 2017 8:58 PM It might help if you explained the technical reasons, since the reason you would want predicates seems obvious. Consider Text_IO. If File_Type was tagged (which it might have been of defined in Ada 95), then the preferable definition is as described in the RM, 3.2.4(41-51/4): with Ada.IO_Exceptions; package Ada.Text_IO is type File_Type is [tagged] limited private; subtype Open_File_Type is File_Type with Dynamic_Predicate => Is_Open (Open_File_Type), Predicate_Failure => raise Status_Error with "File not open"; subtype Input_File_Type is Open_File_Type with Dynamic_Predicate => Mode (Input_File_Type) = In_File, Predicate_Failure => raise Mode_Error with "Cannot read file: " & Name (Input_File_Type); subtype Output_File_Type is Open_File_Type with Dynamic_Predicate => Mode (Output_File_Type) /= In_File, Predicate_Failure => raise Mode_Error with "Cannot write file: " & Name (Output_File_Type); ... function Mode (File : in Open_File_Type) return File_Mode; function Name (File : in Open_File_Type) return String; function Form (File : in Open_File_Type) return String; ... procedure Get (File : in Input_File_Type; Item : out Character); procedure Put (File : in Output_File_Type; Item : in Character); ... -- Similarly for all of the other input and output subprograms. If tagged is in the declaration of File_Type, this is illegal. One can always use preconditions instead, but that's a lot of extra work and easy to omit in large interfaces. So it would be useful to hear more about the "semantic difficulties" (I remember there were some, but not the details). Especially as you can force constraint checks on derived types, just not on subtypes. (So the implementation work exists, it just isn't useful.) **************************************************************** From: Tucker Taft Sent: Saturday, October 21, 2017 10:00 AM The "technical reasons" relate to the need to perform constraint checks at a call site. This is true for dispatching operations as well -- the checks are performed *before* dispatching to the chosen body. If we start having dispatching operations with non-first-subtypes for parameter subtypes, then you have to be sure that the parameters subtypes in an overriding of a dispatching routine have exactly the same checks required at the point of call. This would imply defining statically matching subtypes of different types, which seemed more of a challenge than we were prepared for when Ada 95 was designed. Now that we have pre/postconditions, etc., we have accepted that wrappers may be necessary in any case. If we are willing to require wrappers due to parameter subtype mismatches, we could probably do that, but it could pose further efficiency challenges for implementations to avoid wrappers when not necessary. **************************************************************** From: Victor Porton Sent: Saturday, October 21, 2017 11:59 AM > Can you give some concrete examples where using (non- > first) subtypes, perhaps with predicates, would be necessary to > implement a given abstraction? https://github.com/vporton/redland-bindings/blob/ada2012/ada/src/rdf-rasqal-query_results.ads Here I define for example: function Get_Binding_Value (Results: Bindings_Query_Results_Type_Without_Finalize'Class; Offset: Natural) return Literal_Type_Without_Finalize; Here Bindings_Query_Results_Type_Without_Finalize is a subtype (with a dynamic predicate) of Query_Results_Type_Without_Finalize. Bindings_* subtype specializes on results which contain so called "bindings". I would like the freedom to write instead: function Get_Binding_Value (Results: Bindings_Query_Results_Type_Without_Finalize; Offset: Natural) return Literal_Type_Without_Finalize; (without 'Class, but with a controlled argument). Honestly speaking, this particular problems seems to be solved by using a class-wide argument instead of a controlling argument. But in other tasks this may probably be not a good solution. ****************************************************************