AdaCore Blog

Calling inherited subprograms in Ada

by Emmanuel Briot

In object-oriented code, it is often the case that we need to call inherited subprograms. Some programing languages make it very easy by introducing a new keyword super (although this approach has its limits for languages that allow multiple inheritance of implementation).

In Ada, things are slightly more complicated. Let's take an example, using the traditional geometric classes that are often found in text books:

type Polygon is tagged private;
procedure Initialize (Self : in out Polygon);

type Square is new Polygon with private;
overriding procedure Initialize (Self : in out Square);

Let's assume now that Square's Initialize needs to call Polygon's Initialize, in addition to doing a number of square specific setups. To do this, we need to use type conversions to change the view of Self, so that the compiler statically knows which Initialize to call. The code thus looks like:

procedure Initialize (Self : in out Square) is
begin
     Initialize (Polygon (Self));  --  calling inherited procedure
     ... square-specific setups
end Initialize;

The main issue with this code (apart from its relative lack of readability) is the need to hard-code the name of the ancestor class. If we suddenly realize that a Square is after all a special case of a Rectangle, and thus decide to add the new rectangle class, the code needs to be changed (and not just in the spec), as in:

type Polygon is tagged private;
procedure Initialize (Self : in out Polygon);

type Rectangle is new Polygon with private;   --  NEW
overriding procedure Initialize (Self : in out Rectangle);  --  NEW

type Square is new Rectangle with private;   --  MODIFIED
overriding procedure Initialize (Self : in out Square);

procedure Initialize (Self : in out Square) is
begin
     Initialize (Rectangle (Self));  --   MODIFIED
     ... square-specific setups
end Initialize;

The last change is easy to forget when one modifies the inheritance tree, and its omission would result in not initializing the Rectangle specific data.

A customer recently asked us how the code should best be organized to limit the risks here. One of the idioms that has been proposed is interesting enough that we felt it was worth putting in this short post. The trick is to always define a Parent subtype every time one extends a type, and use that subtype when calling the inherited procedure. Here is a full example:

package Polygons is
    type Polygon is tagged private;
    procedure Initialize (Self : in out Polygon);
end Polygons;

with Polygons;
package Rectangles is
   subtype Parent is Polygons.Polygon;
   type Rectangle is new Parent with private;
   overriding procedure Initialize (Self : in out Rectangle);
end Rectangles;

with Rectangles;
package Squares is
   subtype Parent is Rectangles.Rectangle;
   type Square is new Parent with private;
   overriding procedure Initialize (Self : in out Square);
end Squares;

package body Squares is
   overriding procedure Initialize (Self : in out Square) is
   begin
      Initialize (Parent (Self));
   end Initialize;
end Squares;

Now, if we want to add an extra Parallelogram class between Polygon and Rectangle, we just need to change the definition of the Parent subtype in the Rectangles package, and no change is needed for the body.

This is not a new syntax nor a new idiom, but is worth thinking about when one is developing a complex hierarchy of types, or at least a hierarchy that is likely to change regularly in the future.

The ARG (the people responsible for the evolution of the languages) have discussed this in the past, and several proposals were studied to make the whole matter easier in future versions of the language. There are no definite proposals right now, nor even an agreement on whether such a feature would be sufficiently useful to be worth changing the language. Such proposals often take a form similar to:

procedure Initialize (Self : in out Rectangle) is
begin
     Initialize (Self'Parent);    --   A new attribute (could also be named Super)

     Initialize (Rectangle'Parent (Self));   --  Similar, but on the type itself
end Initialize;

Let's hear your proposals in the comment, and whether you would need something similar in your own code!

Posted in #Ada   

About Emmanuel Briot

Emmanuel Briot has been with AdaCore between 1998 and 2017. He has been involved in a variety of projects, in particular oriented towards graphical user interfaces, including GtkAda, GPS, XML/Ada, GnatTracker and our internal CRM. He holds an engineering degree from the Ecole Nationale des Telecommunications (Brest, France).