AdaCore / ada-spark-rfcs

Platform to submit RFCs for the Ada & SPARK languages
62 stars 28 forks source link

[RFC] improved generic instantiations #103

Open raph-amiard opened 1 year ago

raph-amiard commented 1 year ago

This is the first draft of the high level RFC summarizing the work we have to do to improve generic instantiations in Ada, and of the low level corresponding RFCs

For the moment some of the sub-referenced RFCs are yet to write, I plan to work on them in the coming days/weeks.

briot commented 1 year ago

On 2023-03-06 12:27, Raphaël AMIARD wrote:

As discussed live with Boris: Agreed that with explicit instantiations the first point is moot. Boris also pointed out that we might want a mechanism to prevent implicit references to instantiations for certain generics.

An example that comes to mind is generics with global state such as GNAT's hash table.

1 - disable implicit instantiations

There is at least one  case where the compiler should emit a warning:    - if we allow implicit instantiations, yet the generic package has a global state (this needs to be defined precisely, but a rough approach is whether there are global objects in the package, including a task)

I suppose a generic subprogram can always have implicit instantiations, with the above rule, since it doesn't have a global state.

It would be easy for programmers to forget to disable implicit instantiations, so the more warnings we have the better.

2 - use type qualification for instantiations

Going back to an example in  the proposal:       function Sum (X: Float_Array) return Float is (Reduce (Fn => "+") (X))

I think it might be better to use syntax like type qualification here, rather than subprogram call:      function Sum (X: Float_Array) return Float is (Reduce'(Fn => "+") (X))

to better differentiate which part is the instantiation and which part is the call

3 - need to implicit instantiations

Although I can see the benefits for implicit instantiations, I can't say this is something I have actually missed in Ada (I do have a number of gripes with generics, but not this particular one).  One place where I wished I had them was as defaults for formal parameters in other generics.  For instance:

generic     type T is private;     with package T_Vectors is new Ada.Containers.Vectors (Element_Type => T, others => <>)            is Ada.Containers.Vectors (Positive, T);     --   Missing syntax for defaults, but you get the idea hopefully package Foo is end Foo;

The part I prefer in this proposal is the "inference of dependent types".  That could ease instantiations (even explicit ones), and seems like a good step forward.  A similar change in recent versions of the language is where we no longer need to specify the type when using "renames".   I like when the user doesn't have to state the "obvious".

4 - generic types

In this proposal, I am less convinced by the "Generic types" part. It seems to me that standard subtypes would work here:     subtype Float_Vector is Ada.Containers.Vectors (Positive, Float).Vector; and we do not need to invent a new syntax like "generic type".  Not that I am a big fan of my proposal either, since we might as well use an explicit instantiation here.

5 - error messages

One issue with implicit instantiations is that the compiler is lacking a name to reference things, and we might end up with pretty obscure error messages as a result.  Although this is orthogonal to the proposal, I think the latter needs to ensure that it is possible to have readable error messages  (thinking of C++ templates errors here).

raph-amiard commented 1 year ago

Hi @briot ! Thanks for the thoughtful feedback.

1 - disable implicit instantiations There is at least one case where the compiler should emit a warning:

  • if we allow implicit instantiations, yet the generic package has a global state (this needs to be defined precisely, but a rough approach is whether there are global objects in the package, including a task)

Yes, that's the point Boris raised live and that I recorded here: https://github.com/AdaCore/ada-spark-rfcs/pull/103#discussion_r1126277934.

I'll make sure to address this point in the specific RFC

I think it might be better to use syntax like type qualification here, rather than subprogram call: function Sum (X: Float_Array) return Float is (Reduce'(Fn => "+") (X))

I agree, in particular with the fact that the syntax is less than optimal. I really didn't think too much about it for the moment, because I know that it's going to be the most discussed topic about the RFC. Ideally I would like to find something that is simultaneously:

  1. Readable and explicit (like your proposal)
  2. Non-ambiguous (unlike both your proposal and mine, in which you cannot determine by syntax alone that it is a structural instantiation)
  3. Uses existing Ada symbols.

I was hoping to leverage brackets that were added recently, but Reduce [Fn => "+"] (X) is still potentially ambiguous (or at least Reduce ["+"] (X) is. I imagine we could imagine a syntax similar to type qualification but with brackets instead, and use that for generic instantiation: Reduce'[Fn => "+"] (X)

In this proposal, I am less convinced by the "Generic types" part. It seems to me that standard subtypes would work here:

The generic types part is meant to acknowledge at the language level that often a generic package's only use is to use a specific type. It's also meant to simplify using those types in specific use cases, because typing the full package name to use a container type is verbose for no reason.

With the subtype syntax, at least if this is just a regular subtype, then you cannot instantiate it, and you lose the shortcut AFAICT.

One issue with implicit instantiations is that the compiler is lacking a name to reference things, and we might end up with pretty obscure error messages as a result. Although this is orthogonal to the proposal, I think the latter needs to ensure that it is possible to have readable error messages (thinking of C++ templates errors here).

It is indeed true that implicit instantiations will make error messages harder to decipher. However,

  1. We do have a name to reference things, it's just a tad longer :) But there is only one Vector (Positive, Positive), and as with structural things in programming languages, the name of this instantiation is Vector (Positive, Positive).

  2. Unlike C++ (at least pre-concepts) we can still do most of the error checking before the instantiation, thanks to Ada generics model. Errors that happen at the instantiation site in Ada are pretty rare.

So all in all I'm not too worried about this specific concern.

The biggest concern is whether this all is actually implementable :) The compilation model for structural generics is still to be defined.

raph-amiard commented 1 year ago

One example of something that would be made much easier with this is Option types:

generic
    type T is private;
package Options is
    type Option(Exists : Boolean) is record
        case Exists is
           when True => Val : T;
           when False => null;
        end case;
    end record;
end Options;

generic type Opt is Options.Option;

A : Opt [Integer] := (Val => 12);
sttaft commented 1 year ago

The aggregate (Val => 12) in your example seems to be presuming that the value of the discriminant "Exists" is being inferred. Is that part of these proposals, or just a typo?