Version 1.1 of ai12s/ai12-0243-1.txt

Unformatted version of ai12s/ai12-0243-1.txt version 1.1
Other versions for file ai12s/ai12-0243-1.txt

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

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


Questions? Ask the ACAA Technical Agent