AdaCore / ada-spark-rfcs

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

Overriding with more informative types #43

Open pjljvandelaar opened 4 years ago

pjljvandelaar commented 4 years ago

When overriding a function, the types of in and out arguments (including return arguments) can be changed according to The Liskov Substitution Principle.

In the example project attached below, the scale function of the Figure interface is a nice example

   function Scale (f : in FigureI; s : in Float) return FigureI is abstract
   overriding function Scale(c : in Circle; s : in Float) return Circle
   overriding function Scale(r : in Rectangle; s : in Float) return Rectangle

In the same example project, I tried to apply the Liskov Substitution Principle on the FigureGenerator and RectangleGenerator

function Generate (f : in FigureGeneratorI) return FigureI'Class is abstract;
overriding function Generate (r : in RectangleGenerator) return RectangleI'Class;

Despite that the compiler is aware of the relation between FigureI'Class and RectangleI'Class, Ada doesn't support that. I get the error messages

        22:15 subprogram "Generate" is not overriding
        15:9 type must be declared abstract or "Generate" overridden
        11:15 subprogram "Generate" is not overriding

I have done a number of experiments (with/without 'Class; with/without access). All failed. Some however with another message

operation can be dispatching in only one type

Note that without the substitution of types everything is fine.

I know of other languages that do allow this. I like the additional info on the return type and less need of type checking and casts. E.g. currently I need this code snippet (taken from the main module of the example project) despite the fact that I know a RectangleGenerator generates not just FigureI but even RectangleI instances.

  declare
      rg : constant RectangleGenerator.RectangleGenerator := RectangleGenerator.Make(3.0,4.0);
      f : constant FigureI'Class := rg.Generate;
   begin
       if f in Rectangle.RectangleI'Class then
         declare
            r : constant Rectangle.RectangleI'Class := Rectangle.RectangleI'Class(f);
         begin
            rectanglePut (r);
         end;
      end if;
  end;

Could Ada also support the Liskov Substitution Principle in more type hierarchies than just the overwriting type hierarchy?

The example project: OO.zip

During a meeting with AdaCore, Nexperia and ESI, we were asked by AdaCore to submit this issue to trigger a discussion to determine whether a RFC is justified.

jere-software commented 4 years ago

@pjljvandelaar I'm not against having covariant return, but I wanted to suggest maybe using a different example to show the problem better. As it stands, if Ada did support covariant return, then nothing in your bottom example declare block would change. The variable "f" would still be of type FigureI'Class no matter what type Generate returned. If "f" were of type Rectangle1'Class, then Ada supports function overloads that differ by type, so a function with the same signature as Generate but that returned a Rectangle1'Class would be legal. It wouldn't technically be an override, but it may not need to be in the example provided.

Ada is in a mostly unique situation from other languages in that it supports both a) function overloads that differ only by return type and b) separte 'Class types from the specific types. That helps cover a lot of the applications where covariant return is nice (such as factories), so having an example that pin points a specific problem really would help the discussion.

raph-amiard commented 4 years ago

@jeremiahbreeden this is because @pjljvandelaar is hitting another limitation of Ada's OO, the infamous:

operation can be dispatching in only one type

Which forces him to use classwide types on his returns from primitives. This issue should probably be fixed first in order to really benefit from covariance in return types.

jere-software commented 4 years ago

@raph-amiard I get that. I'm more focused on an actual specific problem to solve with it. That's definitely a weakness in the language (from a usability standpoint), and I would really like to be able to return a dispatching type in those scenarios, but I'm trying to think of a program space problem that I would need to solve where covariant return (whether in Ada's current form or a form where you can return another dispatching type) would help. Again, I go back to my first statement to the OP: If we replaced all the RectangleI'Class with Rectangle1 and all the FigureI'Class with Figure1 and Ada allowed returning another dispatching type (and auto casting if you want), then the declare block in the example provided still would not really change because the type of "f" is still Figure1 so you would need to the inner declare block to recast it to a Rectangle1. Neither covariant return, nor multiple dispatching types, nor the combination of both really seems to affect the outcome of the example.

I'm definitely for adding covariant return (and for solving the multiple dispatch issue). I was just suggesting providing a more targetted example that showed where such a change would actually change the example code. I think it would help bolster the OP's case since no one was responding to the issue as of yet.

raph-amiard commented 4 years ago

@jeremiahbreeden terminology nit: you should not use the term "dispatching type" above. All you want is to return a precise type - not classwide, not dispatching.

I'm definitely for adding covariant return (and for solving the multiple dispatch issue). I was just suggesting providing a more targetted example that showed where such a change would actually change the example code. I think it would help bolster the OP's case since no one was responding to the issue as of yet.

That's fair! I'll see if I can craft such an example. Since this is an issue and not an RFC, we can do this kind of homework here before proposing an RFC.