Ada-Rapporteur-Group / User-Community-Input

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

Interface Implementation Abreviation #35

Open Richard-Wai opened 1 year ago

Richard-Wai commented 1 year ago

It is often the case when deriving a non-interface type from one or more interfaces that we simply want to communicate to the user of such a type that we are implementing the expected interfaces, at a minimum.

Currently Ada requires us to explicitly declare the overriding subprograms inherited from such interfaces when declaring a non-abstract type. When using large interfaces, or a large number of interfaces, or both, this can result in a very large surface of subprogram declarations that don't do much to aid readability. In fact, it can sometimes harm readability as any non-overriding subprograms may get buried in the noise. While we do not typically care much for the number of keystrokes required by the programmer, it could be quite helpful to make a simple statement to the user of a type that it implements the promised interface (something required by the compiler anyways!).

I am proposing a new basic_declarative_item for this purpose, with the name of interface_overriding_clause.

(maybe progenitor_overriding_clause is more accurate?)

3.11-4/1 would be modified as follows:

basic_declarative_item ::= basic_declaration | aspect_clause | use_clause | interface_overriding_clause

And the new clause as follows: interface_overriding_clause ::= overriding all [not null] interface for subtype_mark

subtype_mark refers to a non-interface type that has at least one progenitor type (inherits from at least one interface), declared previously in the same declarative_region in which the interface_overriding_clause item appears. subtype_mark may be an incomplete type. The statement shall not appear more than once for a given subtype_mark within a declarative region.

The interface_overriding_clause has the following effect immediately after it appears:

Imagine you have:

package Interfaces is

  type Interface_A is interface;

  procedure A_1 (A: Interface_A) is abstract;
  procedure A_2 (A: Interface_A) is abstract;
  procedure A_3 (A: Interface_A) is abstract;
  procedure A_4 (A: Interface_A) is abstract;
  procedure A_5 (A: Interface_A) is abstract;
  procedure A_6 (A: Interface_A) is abstract;
  function A_Y (A: Interface_A) is abstract;
  procedure Z (A: Interface_A) is abstract;

  type Interface_B is interface;

  procedure B_1 (B: Interface_B) is abstract;
  procedure Z (B: Interface_B) is null;

end Interfaces;

Traditionally you'd (have to) do this at a minumum:

with Interfaces; use Interfaces;

package Foo_Old is

  type Bar is new Interface_A and Interface_B with null record;

  overriding procedure A_1 (A: Bar);
  overriding procedure A_2 (A: Bar);
  overriding procedure A_3 (A: Bar);
  overriding procedure A_4 (A: Bar);
  overriding procedure A_5 (A: Bar);
  overriding procedure A_6 (A: Bar);
  overriding procedure B_1 (B: Bar);
end Foo;

With this proposal, the following would be exactly equivalent:

with Interfaces; use Interfaces;

package Foo_Old is

  type Bar is new Interface_A and Interface_B with null_record;

  overriding all interface for Bar;
end Foo;

I'm only an aspiring language lawyer, so I won't be surprised if I missed something. I tried my best to propose something here that should be fairly simple to implement. I defer to Taft, Brukardt, and Associates for any fatal errors in this concept.

sttaft commented 1 year ago

procedure Z seems to have disappeared, even though you didn't specify "not null". What is the status of that one?

I don't like the rule where you use the parameter names, etc., from the "first" interface. We worked hard to say the order doesn't matter when you give a list of interfaces, so I would say any place where there is not a perfect match between the interfaces, you have to explicitly, explicitly override.

Other than that, I agree it is an interesting idea. On the other hand, you will ultimately have to provide all of the bodies in the package body, and the reader will see bodies for subprograms in the body of the package but won't necessarily realize they are in fact overriding some interface operation. I suppose you could require that such bodies have the "overriding" keyword, if their spec was implicit due to this "group" overriding construct.

Another issue would be if you want to relax the preconditions, or strengthen the postconditions, there is no place to do that.

Richard-Wai commented 1 year ago

procedure Z seems to have disappeared, even though you didn't specify "not null". What is the status of that one?

In this proposal, "not null" means "do not override any null procedures", basically we are saying, we want to keep Z null. The "not null" refers to what is being overridden implicitly.

I don't like the rule where you use the parameter names, etc., from the "first" interface. We worked hard to say the order doesn't matter when you give a list of interfaces, so I would say any place where there is not a perfect match between the interfaces, you have to explicitly, explicitly override.

Agreed, I felt very iffy about this as well, but it seems that the implicit overrides would need to have parameter names decided somehow, and in a reliable way (specifically with multiple inheritance), so that users could use named parameters..

Other than that, I agree it is an interesting idea. On the other hand, you will ultimately have to provide all of the bodies in the package body, and the reader will see bodies for subprograms in the body of the package but won't necessarily realize they are in fact overriding some interface operation. I suppose you could require that such bodies have the "overriding" keyword, if their spec was implicit due to this "group" overriding construct.

This is the idea, and this would be required anyways. The point of this proposal is to have a very simple way of saying "I am implementing this interface", which would infer that you will be providing bodies - and of course the language already requires that in any case. The conceptual impetuous is that the package specification acts as a sort of documentation for users of the package, and so needing to meticulously declare each override for all interfaces in the spec doesn't really help the user of the package. Ultimately the body will need to provide all the implementations. However I think requiring "overriding" for the bodies in this context makes a lot of sense as well.

To emphasize, this clause is intended to be syntactic sugar, really. We want to automate the overriding declarations that we'd otherwise need.

Another issue would be if you want to relax the preconditions, or strengthen the postconditions, there is no place to do that.

This is exactly why I wrote the rules for this proposed clause to only apply to inherited progenitor user-defined primitive subprograms that were not already overriden before the clause. The idea is, for cases where you need to explicitly do something like that, you'd simply do that before using this clause.