Ada: OOP

Ada's object-oriented model is built on tagged records: types that carry a hidden tag enabling dynamic dispatch. It is deliberately explicit — inheritance is written new ... with, dispatching calls are visible as class-wide operations, and interfaces are separate from state. You always know where the dynamic behavior is.

Tagged Types and Inheritance

tagged marks a record as extensible. A descendant extends it with new ... with record, inheriting fields and primitive operations:

Shape hierarchy with dispatching Area call through the type tag
Fig. 1 — Each tagged object carries a tag; class-wide calls read it to select the overriding subprogram.
package Shapes is
   type Shape is tagged record
      X, Y : Float;                        -- position shared by all shapes
   end record;

   function Area (S : in Shape) return Float is (0.0);   -- a primitive operation

   type Circle is new Shape with record
      Radius : Float;
   end record;

   overriding
   function Area (C : in Circle) return Float is (3.14159 * C.Radius ** 2);
end Shapes;

overriding is optional but strongly recommended: it makes the compiler confirm you actually overrode something — a typo in the name or profile becomes an error instead of a silent new subprogram.

Class-Wide Polymorphism

Shape'Class is the class-wide type: any type in the family. A dispatching call through it picks the concrete Area at run time:

with Ada.Text_IO; use Ada.Text_IO;
with Shapes; use Shapes;

procedure Dispatch_Demo is
   S : Shape'Class := Circle'(X | Y => 0.0, Radius => 2.0);
begin
   --  the compiler does not know which Area runs; the tag decides:
   Put_Line (Float'Image (Area (S)));   -- dispatches to Circle.Area: 12.57
end Dispatch_Demo;

The rule: a class-wide value can hold any family member, but must be initialized (the tag is fixed at creation); a class-wide parameter accepts any family member passed in.

Abstract Types and Interfaces

abstract types have no objects — only descendants. interface types declare contracts without any state at all:

package Render is
   type Drawable is interface;
   procedure Draw (D : in Drawable) is abstract;
   --  every descendant MUST provide Draw

   type Shape is abstract tagged null record;   -- abstract base
   function Area (S : in Shape) return Float is abstract;
end Render;
declare
   type Square is new Render.Shape and Render.Drawable with record
      Side : Float;
   end record;
   --  Square must implement both Area and Draw — or itself be abstract
begin
   null;
end;

Interfaces multiply: a type can implement several (new A and B and C) but extend only one tagged parent — the classic single-inheritance, multi-interface compromise, statically checked.

Where Ada OOP Differs

  • Subprograms live outside the type. Operations are primitive to a type by being declared in the same package — there is no this, no method table syntax, no forced encapsulation of state with behavior in one construct.
  • Dispatch is opt-in. A call through a specific type is bound at compile time; only class-wide calls dispatch. Reading the code tells you which you get.
  • No hidden hierarchy. Every ancestor is written in the type declaration and resolvable through the source.

Next: Contracts — pre- and postconditions on all of this.