Ada-Rapporteur-Group / User-Community-Input

Ada User Community Input Working Group - Github Mirror Prototype
26 stars 1 forks source link

Non-first subtypes for controlling parameters #57

Open ARG-Editor opened 1 year ago

ARG-Editor commented 1 year ago

This issue continues an unfinished topic from Ada 2022. This issue was created to fulfill the ARG resolution of November 10, 2022.


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.

For Ada 2022, two solutions were proposed:

AI12-0243-1 contains a solution essentially which repeals the requirement for static matching for overriding controlling parameters. This has the downside of forcing the checks to runtime for dispatching calls (the subtype of the actual subprogram is not known at the point of the call). This is consistent with the behavior of Pre, which makes sense since predicates are not designed to be inherited.

AI12-0243-2 contains a solution which adds class-wide predicates. Class-wide predicate expressions are required to follow rules similar to those for Pre'Class. This makes them safe to inherit for all extensions. We then extend static matching to allow conforming class-wide predicates, and allow subtypes as controlling parameters (this latter change also allows constrained subtypes on parameters, which have to match). This model preserves the ability to make subtype checks for dispatching calls at the call site (in that way, it is like Pre'Class), but it is obviously more complex.

We were unable to decide between these two solutions in time to include one in Ada 2022

ARG-Editor commented 1 year ago

Here is my personal take on this topic.

Eliminating duplication should always be a goal for a programming language, so the ability to name conditions for a subprogram (essentially user-defined constraints) is an important one. Denying that tool simply because the parameter is controlling is bad.

However, since these are OOP types, I don't want to adopt a solution to this problem which necessarily does not work with LSP. For AI12-0243-1, one has to use Pre'Class to get LSP; the use of predicates is not possible as they are more like Pre (which does not follow LSP). It is of course possible to use Pre and/or predicates to get the effect of LSP, but doing so requires carefully duplicating the preconditions/predicates on inherited routines. That clearly is a maintenance hazard; should it become necessary to modify a predicate, that modification would have to be repeated in each descendant. We developed Pre'Class to avoid this hazard, and it seems wrong to re-introduce the hazard (requiring a programmer to duplicate a condition in many Pre'Class predicates rather than using a single subtype with a predicate is another form of that hazard).

AI12-0243-2, which works like Pre'Class, avoids the hazard and ensures that LSP is followed.

Note that for maximum flexibility, we could combine the proposals, allowing non-matching subtypes that would have to be checked after dispatching along with class-wide predicates (and matching constraints) that could be checked at the dispatching call site. That does, however, put more of the burden on the programmer.

sttaft commented 1 year ago

I would probably go with class-wide predicates, to be consistent with other uses of class-wide aspects. However, I would require class-wide predicates be nonoverridable, and rely on the usual ability to override the functions called from the class-wide aspect to change what is actually being checked. I would also require that it be specified on the root type's operation if it is going to be specified at all. In this sense the predicate is "baked into" the operation

OneWingedShark commented 5 months ago

This issue continues an unfinished topic from Ada 2022. This issue was created to fulfill the ARG resolution of November 10, 2022.

We were unable to decide between these two solutions in time to include one in Ada 2022

Perhaps an Aspect _ControllingParameter of a subprogram, which takes the parameter-name (or, in the case of functions 'Result), thus overriding the default first-parameter rule. This would preserve extant code while allowing the new functionality.

sttaft commented 5 months ago

Perhaps an Aspect _ControllingParameter of a subprogram, which takes the parameter-name (or, in the case of functions 'Result), thus overriding the default first-parameter rule. This would preserve extant code while allowing the new functionality.

There is no first-parameter rule associated with this issue. Perhaps this comment is meant for the AI about using object.operation notation more widely?