Ada

If you’re young, you might not have heard of the Ada language, but you won’t regret learning it.

Overview

Ada was designed in the late 1970s, first released in 1983, and was the first object-oriented language to be internationally standardized (in 1995). The language was designed to facilitate the design of large, long-lived, efficient, reliable software systems. It is particularly useful in concurrent, distributed, real-time, and embedded systems. It is an extremely safe language and is thus primarily used today in avionics, air traffic control, rail systems, embedded systems, medical devices, and communication.

As a general purpose language, you can use it in virtually every interesting area of Computer Science.

Some resources you’ll want to know about are:

The language was designed for complex tasks. Readability was an important design goal, so many people find it verbose. But others consider this is a good thing. Getting work done in Ada is a very pleasant experience because many aspects of good software engineering are enforced by the language.

The language has evolved over time, with the following major versions:

VersionWikibookReference ManualRationale or OverviewStandard
Ada 83 Info RM '83 ISO/IEC 8652:1987
Ada 95 Info ARM 95
AARM 95
Rat 95 ISO/IEC 8652:1995(E) with COR.1:2000
Ada 2005 Info ARM 2005
AARM 2005
Rat 2005 ISO/IEC 8652:1995(E) with COR.1:2001 and AMD.1:2007
Ada 2012 Info ARM 2012
AARM 2012
Rat 2012 ISO/IEC 8652:2012(E)
Ada 2022 Info ARM 2022
AARM 2022
Overview ISO/IEC 8652:2023

The notes on this page are designed to only get you started and cover only the most basic elements of the language. I have notes on Ada concurrency elsewhere, but even these cover only a small subset of the language.

For a more modern and much better tutorial, see the Wikibook or learn.adacore.com.

Hello, World

The simplest kind of program is a simple procedure with no parameters:

hello.adb
with Ada.Text_IO;

procedure Hello is
begin
   Ada.Text_IO.Put_Line ("Hello, world");
end Hello;

The modern way to execute Ada programs is to use the package manager Alire. If you are familiar with Rust, Alire is equivalent to Cargo. If you set Alire up correctly, and ask it to build a binary crate for your hello program, you should be able to run it with:

$ alr run
 Building ada=0.0.1/ada.gpr...
Compile
   [Ada]          hello.adb
Bind
   [gprbind]      hello.bexch
   [Ada]          hello.ali
Link
   [link]         hello.adb
 Build finished successfully in 0.13 seconds.
Hello, world

Since the run command produces a lot of output, you can just do alr build, which puts the executable in the bin folder. Then just run:

$ bin/hello
Hello, world

More Simple Programs

Here’s a program that prints some lucky numbers:

triple.adb
-------------------------------------------------------------------------------
--  This program writes out a table of all Pythagorean triples whose elements
--  are in the range 1..100. Each of the triples are written on a single line
--  of text with each value right-justified in a text field six characters
--  wide.
-------------------------------------------------------------------------------

with Ada.Text_IO, Ada.Integer_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO;

procedure Triple is
begin
   Put_Line ("     A     B     C");
   Put_Line ("------------------");
   for C in 1 .. 100 loop
      for B in 1 .. C loop
         for A in 1 .. B loop
            if A ** 2 + B ** 2 = C ** 2 then
               Put (A, Width => 6);
               Put (B, Width => 6);
               Put (C, Width => 6);
               New_Line;
            end if;
         end loop;
      end loop;
   end loop;
end Triple;

And more numbers you know:

fib.adb
-------------------------------------------------------------------------------
--  This program prints out Fibonacci numbers in octal, decimal and hexadecimal
--  in reverse order. The number of Fibonacci numbers that the user wants
--  to see is input on the command line, but if no arguments are given on the
--  command line then the program will prompt the user to input that value.
--
--  This script is meant only to demonstrate a few Ada features, such as user-
--  defined exceptions, enumeration types, the 'Pos attribute, attributes, the
--  reverse keyword, the case statement, and assignment to array slices.
-------------------------------------------------------------------------------

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Command_Line;
use Ada.Text_IO, Ada.Integer_Text_IO, Ada.Command_Line;

procedure Fib is

   Number_Of_Values_Requested : Integer;

   --  In Ada there are a number of predefined exceptions, but we can declare
   --  our own. Too_Few will be raised if the user does not ask for at least
   --  two Fibonacci numbers, and Bad_Argument will be raised if the program
   --  can not make sense out of the command line argument:

   Too_Few, Bad_Argument : exception;

   type Number_List is array (Integer range <>) of Integer;
   type Number_System is (Octal, Decimal, Hex);

   --  The function Get_User_Input returns the user's "input" as follows:
   --  First it checks to see if any argument was entered on the command
   --  line and if so attempts to convert that value to a natural number.
   --  If the command line has no parameters the user is prompted for an
   --  input. In either case, the value input must be greater than or
   --  equal to 2; the function raises Too_Few if it is not.

   function Get_User_Input return Integer is
      Size : Integer;
   begin
      if Argument_Count > 0 then
         Size := Natural'Value (Argument (1));
      else
         Put ("How many numbers do you want (minimum = 2)? ");
         Get (Size);
      end if;
      if Size < 2 then
         raise Too_Few;
      end if;
      return Size;
   exception
      --  Natural'Value raises a Constraint_Error if the string cannot be
      --  converted to a natural. Let's repackage this to a nicer exception
      when Constraint_Error => raise Bad_Argument;
   end Get_User_Input;

   --  Compute (Size) finds and displays the first Size Fibonacci numbers.
   --  It first allocates an array of just the right size to store the
   --  numbers, then loads the array by performing the straightforward
   --  calculations. Then the array is printed backwards three times
   --  with different bases, using the helper function Display.

   procedure Compute (Size : Integer) is
      Fibs : Number_List (1 .. Size);

      procedure Display (B : Number_System) is
      begin
         Put ("Fibonaccis in " & B'Image & " backwards: ");
         New_Line (2);
         for I in reverse Fibs'Range loop
            case B is
               --  The order of arguments should be value, width, base. But
               --  you can use argument association to change things up a bit.
               when Decimal => Put (Fibs (I), 15, 10);
               when Octal => Put (Fibs (I), Base => 8, Width => 15);
               when Hex => Put (Base => 16, Item => Fibs (I), Width => 15);
            end case;
            New_Line;
         end loop;
         New_Line (2);
      end Display;

   begin
      Fibs (1 .. 2) := [1, 1];
      for I in 3 .. Size loop
         Fibs (I) := Fibs (I - 1) + Fibs (I - 2);
      end loop;
      Display (Octal);
      Display (Decimal);
      Display (Hex);
   end Compute;

begin
   Number_Of_Values_Requested := Get_User_Input;
   Compute (Size => Number_Of_Values_Requested);
   Put_Line ("Program successfully completed");
exception
   when Too_Few => Put_Line ("Not enough values requested");
   when Bad_Argument => Put_Line ("Illegal command line argument");
   when Constraint_Error => Put_Line ("I can't find that many");
   when Data_Error => Put_Line ("That's not a decent response");
end Fib;

And prime numbers, because you may have expected to see them:

prime_sieve.adb
------------------------------------------------------------------------------
--  This program computes and prints all the prime numbers up to and including
--  a number which is provided as the sole argument on the command line, or,
--  if no command  line arguments are given, input by the user in response to
--  a prompt, and then reports how many primes it found.
------------------------------------------------------------------------------

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Command_Line;
use Ada.Text_IO, Ada.Integer_Text_IO, Ada;

procedure Prime_Sieve is

   Limit    : Natural;                    -- will find primes up to this
   How_Many : Natural;                    -- how many primes were found

   procedure Compute_Primes (Up_To : Natural; Count : out Natural) is
      Sieve : array (2 .. Up_To) of Boolean := (others => True);
   begin
      Count := 0;
      for I in Sieve'Range loop
         if Sieve (I) then
            Put (Item => I, Width => 12, Base => 10);
            Count := Count + 1;
            declare                       -- start a block
               K : Natural := I;          -- variable local to the block
            begin
               while K <= Sieve'Last - I loop
                  K := K + I;             -- go to next multiple
                  Sieve (K) := False;     -- check off this multiple
               end loop;
            end;                          -- after the block K does not exist
            if Count mod 6 = 0 then       -- write six primes per row
               New_Line;
            end if;
         end if;
      end loop;
      New_Line (2);
   end Compute_Primes;

begin
   if Command_Line.Argument_Count = 0 then
      -- If no command line argument, prompt the user for input
      Put ("Find primes up to what number? ");
      Get (Limit);
   else
      Limit := Natural'Value (Command_Line.Argument (1));
   end if;
   Compute_Primes (Up_To => Limit, Count => How_Many);
   Put_Line ("I found" & How_Many'Image & " primes.");
exception
   when Constraint_Error => Put_Line ("Illegal command line argument");
   when Data_Error => Put_Line ("I don't understand that input");
end Prime_Sieve;

Language Structure

The Ada language is comprised of two parts:

Core Language

Required of all implementations.

ARM Clauses 1-13, and Annexes A, B, and J.

Specialized Needs Annexes

Not required of an implementation.

ARM Annexes C, D, E, F, G, and H.

You should browse the reference manual table of contents now to see what these clauses and annexes refer to.

Reserved Words

Ada is clearly not a small language.

abort
abs
abstract
accept
access
aliased
all
and
array
at
begin
body
case
constant
declare
delay
delta
digits
do
else
elsif
end
entry
exception
exit
for
function
generic
goto
if
in
interface
is
limited
loop
mod
new
not
null
of
or
others
out
overriding
package
parallel
pragma
private
procedure
protected
raise
range
record
rem
renames
requeue
return
reverse
select
separate
some
subtype
synchronized
tagged
task
terminate
then
type
until
use
when
while
with
xor

Program Structure

An Ada program is made up of a collection of program units, which can be either subprograms or packages. Ada units are designed to have well specified interfaces so they are highly reusable.

Here is a trivial example of a program made up of a package exporting a type Float_Array and two functions, Mean and Variance:

stats.ads
package Stats is

   type Float_Array is array (Integer range <>) of Float;

   function Mean (A : Float_Array) return Float;
   function Variance (A : Float_Array) return Float;

end Stats;

To show off information hiding features, the package contains a local, non-exported utility function called Sum:

stats.adb
package body Stats is

   function Sum (A : Float_Array) return Float is (A'Reduce ("+", 0.0));

   function Mean (A : Float_Array) return Float is
      (Sum (A) / Float (A'Length));

   function Variance (A : Float_Array) return Float is
      M            : constant Float := Mean (A);
      Square_Diffs : Float_Array (A'Range);
   begin
      Square_Diffs := [for I in A'Range => (A (I) - M) ** 2];
      return Mean (Square_Diffs);
   end Variance;

end Stats;

Here’s a command line application that shows off the functions:

show_stats.adb
with Ada.Text_IO, Ada.Float_Text_IO, Stats;
use Ada.Text_IO, Ada.Float_Text_IO, Stats;

procedure Show_Stats is
   A : constant Float_Array := [3.0, 5.0, 8.0, 2.0];
begin
   Put (Mean (A)); New_Line;
   Put (Variance (A)); New_Line;
end Show_Stats;

There was a lot going on there!

Exercise: Add functions for median and mode.

Types

See Section 3 of the Reference Manual, particularly the note at the bottom of Section 3.2 which gives a really nice hierarchy of the kinds of types in the type system, and a few of the built-in (standard) types. Here’s a summary of that information:

                     All Types
                         │
       ┌─────────────────┴─────────────────┐
       │                                   │
   Elementary                            Composite
       │                                   │
   ┌───┴──────┐                      ┌─────┴─────────┐
   │          │                      │               │
Scalar      Access               Untagged          Tagged
│           ├─ To Object         ├─ Array          ├─ Nonlimited Tagged Record
│           └─ To Subprogram     │  └─ String      └─ Limited Tagged
│                                ├─ Record            ├─ Limited Tagged Record
├─ Discrete                      ├─ Task              └─ Synchronized Tagged
│  ├─ Enumeration                └─ Protected            ├─ Tagged Task
│  │  ├─ Character                                       └─ Tagged Protected
│  │  └─ Boolean
│  └─ Integer
│     ├─ Signed
│     └─ Modular
│
└─ Real
   ├─ Floating Point
   └─ Fixed Point
      ├─ Ordinary
      └─ Decimal

Here’s a program that illustrates a few of the simplest types in use (by simple we mean that it avoids function types, variant records, tagged records, and anything for concurrency):

simple_types.adb
-------------------------------------------------------------------------------
--  This program doesn't do anything useful; it just illustrates some of Ada's
--  simple types and some interesting aspects of the type system. It is far
--  from complete. So much is missing, but it's a start.
-------------------------------------------------------------------------------

with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Float_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO, Ada.Float_Text_IO;

with Ada.Numerics.Elementary_Functions, Ada.Assertions;
use Ada.Numerics.Elementary_Functions, Ada.Assertions;

procedure Simple_Types is

   --  Simple object declarations with some predefined numeric types:

   Count   : Integer := 0;                     -- vars MAY have initializers
   Pi      : constant Float := Arccos (-1.0);  -- consts MUST have initializers
   Tau     : constant Float := 2.0 * Pi;       -- can reference previous ones
   K       : constant Integer := 2 ** 10;      -- 1K = 1024

   --  Enumeration types. type Boolean = (False, True) is predefined. Here are
   --  some we define ourselves:

   type Color is (Black, Blue, Green, Cyan, Red, Magenta, Yellow, White);
   type Day is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);

   --  Subtypes. These are not "real" types, in that objects you declare
   --  as having the subtype really "have" the original type. They may lead
   --  to runtime checks. The subtypes Natural and Positive of Integer are
   --  predefined.

   subtype Reddish is Color range Red .. Yellow;
   subtype Weekday is Day range Mon .. Fri;
   subtype Byte is Integer range 0 .. 255;

   --  A subtype that does not restrict a type is just an alias.

   subtype Int is Integer;

   --  Constrained Array Types. Being able to define an array's index type as
   --  an enumeration, rather than being restricted to 0..N-1, sets Ada apart
   --  from a lot of other languages.

   type Trio is array (1 .. 3) of Byte;
   type Color_Chart is array (0 .. K - 1) of Color;
   type Time_Card is array (Day) of Float;
   type Short_Time_Card is array (Weekday) of Float;

   --  Unconstrained array type declarations are just fine. These MAY be
   --  constrained in a subtype declaration, but MUST be constrained in an
   --  object declaration, since an object declaration allocates memory.

   type Matrix is array (Integer range <>, Integer range <>) of Float;
   subtype Mat_33 is Matrix (1 .. 3, 1 .. 3);

   Grid  : Matrix (1 .. 8, 1 .. 8);
   Ident : constant Mat_33 := [
      [1.0, 0.0, 0.0],
      [0.0, 1.0, 0.0],
      [0.0, 0.0, 1.0]
   ];

   --  Enumeration types may contain identifiers or character literals.

   type Roman_Digit is ('I', 'V', 'X', 'L', 'C', 'D', 'M');
   type Roman is array (Positive range <>) of Roman_Digit;

   --  The following are predefined (i.e. they appear in package Standard):
   --
   --  subtype Positive is Integer range 1..Integer'Last
   --  type String is array (Positive range <>) of Character;

   --  Character array subtypes and objects.

   Prompt       : String (1 .. 50);
   Greeting     : constant String (1 .. 5) := "Hello";
   subtype Line is String (1 .. 80);
   C_Comment    : constant Line := [1 => '/', 2 .. 79 => '*', 80 => '/'];
   Ninety_Eight : constant Roman := "XCVIII";

   --  Another enumeration type:

   type Month_Name is (
      Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
   );

   --  Record types:

   type Date is record
      Day   : Positive range 1 .. 31;
      Month : Month_Name;
      Year  : Integer;
   end record;

   type Point is record
      X : Float := 0.0;                       -- fields may have initial values
      Y : Float := 0.0;
   end record;

   --  Record types can have discriminants. An object S of type Square has two
   --  fields: S.Side and S.Data. The discriminant Side must be given a value
   --  when the object is declared and is read only.

   type Square (Side : Integer) is record
      Data : Matrix (1 .. Side,  1 .. Side);
   end record;

   subtype Three_Dimensional_Transform is Square (4);
   A_Funny_Thing : Square (2) := (
      2,
      [[1.0, 3.2], [9.9, 3.88E-28]]
   );

   --  You can specify a default for the discriminant:

   subtype Buffer_Size is Positive range 256 .. 65536;

   type Buffer (Size : Buffer_Size := 256) is record
      Position : Positive;
      Value    : String (1 .. Size);
   end record;

   --  If a value is specified for an object of a record type with a default
   --  discriminant, then it is a normal constrained object...

   K_Buffer : Buffer (1024);                  -- 1024 elements

   --  ...but if no value is specified, the object is unconstrained, meaning
   --  you can actually change the discriminant at run time (provided that
   --  assignments are made to all fields simultaneously).

   My_Buffer : Buffer;                        -- 256 elements but changeable

   --  Arbitrary linked structures require mutually recursive types. Since
   --  Ada elaborates declarations strictly in order, some types must be
   --  declared before they are defined.

   type Node;                                 -- incomplete type declaration
   type Link is access Node;                  -- use of incomplete declaration

   type Node is record                        -- completion
      Value    : Integer;
      Previous : Link;
      Next     : Link;
   end record;

   --  Here are some more variable declarations:
   T      : Time_Card;
   D      : Date;
   Chart  : Color_Chart;
   S      : Link;
   P      : Point;

begin
   Count := Count + 1;
   Put_Line ("Count = " & Count'Image & ", K = " & K'Image);
   Put_Line ("See you every " & Tue'Image & " and " & Thu'Image);

   --  Printing floats
   Put (Tau, Exp => 0); New_Line;
   Put (Tau, Fore => 7, Aft => 6, Exp => 5); New_Line;

   --  Enumeration type attributes
   Assert (Color'Last = White);
   Assert (Color'Val (2) = Green);
   Assert (Day'Pos (Sat) = 5 and then Day'Pos (Sun) = 6);

   --  Array Aggregates
   T := [2.6, 10.2, 9.3, 8.0, 0.0, 0.0, 1.0];
   Assert (T (Wed) = 9.3);
   Chart := [Black, Cyan, Red, Blue, others => Magenta];
   Assert (Chart (3) = Blue);

   --  Records
   D := (1, Jul, 1991); Put_Line (D'Image);
   D := (Month => Jul, Day => 1, Year => 1991); Put (D.Year);
   D := (1, Year => 1991, Month => Jul);
   Assert (D.Month = Jul);
   P := (3.0, -234.22);
   Put (P.X);

   --  Strings
   Put (Greeting);
   New_Line;
   Put_Line (C_Comment (68 .. 80));           -- this is called a slice
   Assert (Ninety_Eight'Length = 6);

   --  Pointers
   S := new Node;
   S.all.Value := 3;
   S.Value := 3;                              -- ".all" is optional in middle
   S.Next := new Node'(Value => 10, Next => null, Previous => S);

   --  The Image attribute works even for arrays of arrays
   Put_Line (Ident'Image);

end Simple_Types;

Expressions

TODO - numeric literals

TODO - null

TODO - string literals

TODO - aggregates

TODO - names

TODO - allocators

TODO - parenthesized expressions

TODO - conditional expressions

TODO - quantified expressions

TODO - operators

For more, see the expressions lesson at learn.adacore.com.

Statements

We’ve seen a lot of the statements above, so for completeness, here’s a list of every kind of Ada statement:

null
assignment
goto
code
if
case
for
while
parallel
block
exit
procedure call
return
entry call
conditional entry call
timed entry call
requeue
delay
delay until
abort
raise
accept
select
selective accept
asynchronous select

Generics

Here’s another classic pedagogical exercise: creating an abstract data types for stacks, from first principles, using linked lists. The stack element type will be generic. First, the package specification:

unbounded_stacks.ads
generic
   type Element is private;
package Unbounded_Stacks is

   type Stack is limited private;

   Overflow, Underflow, No_Top : exception;

   function Is_Empty (S : Stack) return Boolean;
   function Size (S : Stack) return Natural;
   procedure Push (S : in out Stack; E : Element);
   function Top (S : Stack) return Element;
   procedure Pop (S : in out Stack);
   procedure Pop (S : in out Stack; E : out Element);

private

   type Node;
   type Stack is access Node;
   type Node is record
      Data : Element;
      Link : Stack;
   end record;

end Unbounded_Stacks;

Next, the package body, in a separate file:

unbounded_stacks.adb
package body Unbounded_Stacks is

   function Is_Empty (S : Stack) return Boolean is (S = null);

   function Size (S : Stack) return Natural is
      (if S = null then 0 else 1 + Size (S.Link));

   procedure Push (S : in out Stack; E : Element) is
   begin
      S := new Node'(E, S);
   exception
      when Storage_Error => raise Overflow;
   end Push;

   function Top (S : Stack) return Element is
   begin
      if Is_Empty (S) then
         raise No_Top;
      end if;
      return S.Data;
   end Top;

   procedure Pop (S : in out Stack) is
   begin
      if Is_Empty (S) then
         raise Underflow;
      end if;
      S := S.Link;
   end Pop;

   procedure Pop (S : in out Stack; E : out Element) is
   begin
      if Is_Empty (S) then
         raise Underflow;
      end if;
      E := S.Data;
      S := S.Link;
   end Pop;

end Unbounded_Stacks;

Now, if we want to add new functionality to our ADT, we can use a child package. This is a pretty decent illustration of when to use child packages, since it’s probably best not to pollute the basic stack package with I/O, which not everyone may want:

unbounded_stacks-io.ads
------------------------------------------------------------------------------
--  unbounded_stacks-io.ads
--
--  A generic child package which enables output for unbounded stacks.
--
--  Generic Parameters:
--
--    Put (F, E)     procedure to display element E to file F.
--
--  Operations:
--
--    Put (F, S)     write S to file F in the form  <x1 x2 ... xn>  from top
--                   to bottom.
--    Put (S)        Same as Put (Current_Output, S)
------------------------------------------------------------------------------

with Ada.Text_IO;
use Ada.Text_IO;

generic
   with procedure Put (F : File_Type; E : Element);
package Unbounded_Stacks.IO is

   procedure Put (F : File_Type; S : Stack);
   procedure Put (S : Stack);

end Unbounded_Stacks.IO;

Here’s the body of the child package:

unbounded_stacks-io.adb
package body Unbounded_Stacks.IO is

   procedure Put (F : File_Type; S : Stack) is
      T : Stack := S;
   begin
      Put (F, "< ");
      while T /= null loop
         Put (F, T.Data);
         Put (' ');
         T := T.Link;
      end loop;
      Put (F, '>');
   end Put;

   procedure Put (S : Stack) is
   begin
      Put (Current_Output, S);
   end Put;

end Unbounded_Stacks.IO;

To exercise the stack package, we write a little driver:

test_unbounded_stacks.adb
with Ada.Text_IO, Unbounded_Stacks, Unbounded_Stacks.IO;
use Ada.Text_IO;

procedure Test_Unbounded_Stacks is

   package My_Stacks is new Unbounded_Stacks (Character);
   use My_Stacks;

   package My_Stacks_IO is new My_Stacks.IO (Put);
   use My_Stacks_IO;

   S : Stack;

   procedure Test is
      Option : Character;
      Data : Character;
   begin
      loop
         Put (S);
         Put_Line (" Length =" & Size (S)'Image);
         Put ("Empty pusH Pop Quit: ");
         Get (Option);
         New_Line;
         case Option is
            when 'h' =>
               begin
                  Put ("push what? "); Get (Data); Push (S, Data);
               exception
                  when Overflow => Put_Line ("Cannot push onto full stack");
               end;
            when 'p' =>
               begin
                  Pop (S, Data); Put (Data); Put_Line (" popped");
               exception
                  when Underflow => Put_Line ("Cannot pop from empty stack");
               end;
            when 'e' =>
               Put (if Is_Empty (S) then "Empty" else "Not Empty");
            when 'q' => exit;
            when others => null;
         end case;
         New_Line;
      end loop;
   end Test;

begin
   Test;
end Test_Unbounded_Stacks;

Variant Records

Ada’s sum type is the variant record. Here’s the classic shape type, where a shape can be a circle or a rectangle:

variant_demo.adb
-- An approach to shapes using variant records

with Ada.Text_IO, Ada.Numerics;
use Ada.Text_IO, Ada.Numerics;

procedure Variant_Demo is
   type Color is (Black, Blue, Green, Cyan, Red, Magenta, Yellow, White);

   type Shape_Kind is (Circle, Rectangle);

   type Shape (Kind : Shape_Kind := Circle) is record
      Fill : Color;
      case Kind is
         when Circle => Radius : Float;
         when Rectangle => Width, Height : Float;
      end case;
   end record;

   function Area (S : Shape) return Float is
   begin
      case S.Kind is
         when Circle => return Pi * S.Radius ** 2;
         when Rectangle => return S.Width * S.Height;
      end case;
   end Area;

   function Perimeter (S : Shape) return Float is
   begin
      case S.Kind is
         when Circle => return 2.0 * Pi * S.Radius;
         when Rectangle => return 2.0 * (S.Width + S.Height);
      end case;
   end Perimeter;

   type Shape_Array is array (Positive range <>) of Shape;

   Shapes : constant Shape_Array := [
      Shape'(Kind => Circle, Fill => Red, Radius => 10.0),
      Shape'(Kind => Rectangle, Fill => Blue, Width => 5.0, Height => 10.0)
   ];

begin
   for Shape of Shapes loop
      Put_Line (Shape.Kind'Image & ": Area = " & (Area (Shape)'Image) &
         ", Perimeter = " & (Perimeter (Shape)'Image));
   end loop;
end Variant_Demo;

Object Oriented Features

The term object-oriented programming has strayed from its original meaning to a world of in which objects have state and behavior, and in which the types (classes) are emphasized over the the functions. Ada does this with tagged records. (The term “tagged” makes sense here—the dynamic polymorphism of modern OOP happens because values are tagged with their “class”):

tagged_demo.adb
with Ada.Text_IO, Ada.Numerics, Ada.Tags;
use Ada.Text_IO, Ada.Numerics, Ada.Tags;

procedure Tagged_Demo is
   type Color is (Black, Blue, Green, Cyan, Red, Magenta, Yellow, White);

   package Shapes is

      type Shape is abstract tagged record
         Fill : Color;
      end record;

      function Area (S : Shape) return Float is abstract;
      function Perimeter (S : Shape) return Float is abstract;

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

      overriding function Area (C : Circle) return Float;
      overriding function Perimeter (C : Circle) return Float;

      type Rectangle is new Shape with record
         Width, Height : Float;
      end record;

      overriding function Area (R : Rectangle) return Float;
      overriding function Perimeter (R : Rectangle) return Float;
   end Shapes;

   package body Shapes is
      overriding function Area (C : Circle) return Float is
         (Pi * C.Radius ** 2);

      overriding function Perimeter (C : Circle) return Float is
         (2.0 * Pi * C.Radius);

      overriding function Area (R : Rectangle) return Float is
         (R.Width * R.Height);

      overriding function Perimeter (R : Rectangle) return Float is
         (2.0 * (R.Width + R.Height));
   end Shapes;

   use Shapes;

   My_Shapes : constant array (Positive range <>) of access Shape'Class := [
      new Circle'(Fill => Red, Radius => 10.0),
      new Rectangle'(Fill => Blue, Width => 5.0, Height => 10.0)
   ];

begin
   for Shape of My_Shapes loop
      Put_Line (Expanded_Name (Shape'Tag)
      & ": Area = " & Area (Shape.all)'Image
      & ", Perimeter = " & Perimeter (Shape.all)'Image);
   end loop;
end Tagged_Demo;
Variant Records vs. Tagged Records

With variant records (sum types), we see:

  • A single type with multiple variants
  • Logic in external functions that operate on the type
  • Use of discriminated unions and case statements to branch on the variant
  • A more procedural/functional programming style

With tagged records (OOP), we see:

  • Multiple types in a class hierarchy
  • Methods that belong to the types
  • Use of inheritance and dynamic dispatching to select the appropriate method
  • A more object-oriented programming style

Contracts

TODO

Containers

TODO

Concurrency

Concurrency features have been built into Ada since its inception, and is one of the language's key strengths. The topic is so large that full-length books have been written about Ada concurrency.

My notes are on a separate page.

Testing

TODO

Standard Library

Ada features a really cool and comprehensive standard library. The full list of units is summarized at the beginning of Annex A of the Reference Manual. The library is organized into a hierarchy, which can be used to organize code and provide a namespace for identifiers.

There are three root library units: Ada, System, and Interfaces. Here’s a listing for scanning. See the Reference Manual index to dig deeper.

Ada — A.2
    Assertions — 11.4.2
    Asynchronous_Task_Control — D.11
    Calendar — 9.6
        Arithmetic — 9.6.1
        Formatting — 9.6.1
        Time_Zones — 9.6.1
    Characters — A.3.1
        Conversions — A.3.4
        Handling — A.3.2
        Latin_1 — A.3.3
    Command_Line — A.15
    Complex_Text_IO — G.1.3
    Containers — A.18.1
        Bounded_Doubly_Linked_Lists — A.18.20
        Bounded_Hashed_Maps — A.18.21
        Bounded_Hashed_Sets — A.18.23
        Bounded_Indefinite_Holders — A.18.32
        Bounded_Multiway_Trees — A.18.25
        Bounded_Ordered_Maps — A.18.22
        Bounded_Ordered_Sets — A.18.24
        Bounded_Priority_Queues — A.18.31
        Bounded_Synchronized_Queues — A.18.29
        Bounded_Vectors — A.18.19
        Doubly_Linked_Lists — A.18.3
        Generic_Array_Sort — A.18.26
        Generic_Constrained_Array_Sort — A.18.26
        Generic_Sort — A.18.26
        Hashed_Maps — A.18.5
        Hashed_Sets — A.18.8
        Indefinite_Doubly_Linked_Lists — A.18.12
        Indefinite_Hashed_Maps — A.18.13
        Indefinite_Hashed_Sets — A.18.15
        Indefinite_Holders — A.18.18
        Indefinite_Multiway_Trees — A.18.17
        Indefinite_Ordered_Maps — A.18.14
        Indefinite_Ordered_Sets — A.18.16
        Indefinite_Vectors — A.18.11
        Multiway_Trees — A.18.10
        Ordered_Maps — A.18.6
        Ordered_Sets — A.18.9
        Synchronized_Queue_Interfaces — A.18.27
        Unbounded_Priority_Queues — A.18.30
        Unbounded_Synchronized_Queues — A.18.28
        Vectors — A.18.2
    Decimal — F.2
    Direct_IO — A.8.4
    Directories — A.16
        Hierarchical_File_Names — A.16.1
        Information — A.16
    Dispatching — D.2.1
        EDF — D.2.6
        Non_Preemptive — D.2.4
        Round_Robin — D.2.5
    Dynamic_Priorities — D.5.1
    Environment_Variables — A.17
    Exceptions — 11.4.1
    Execution_Time — D.14
        Group_Budgets — D.14.2
        Interrupts — D.14.3
        Timers — D.14.1
    Finalization — 7.6
    Float_Text_IO — A.10.9
    Float_Wide_Text_IO — A.11
    Float_Wide_Wide_Text_IO — A.11
    Integer_Text_IO — A.10.8
    Integer_Wide_Text_IO — A.11
    Integer_Wide_Wide_Text_IO — A.11
    Interrupts — C.3.2
        Names — C.3.2
    IO_Exceptions — A.13
    Iterator_Interfaces — 5.5.1
    Locales — A.19
    Numerics — A.5
        Big_Numbers — A.5.5
            Big_Integers — A.5.6
            Big_Reals — A.5.7
        Complex_Arrays — G.3.2
        Complex_Elementary_Functions — G.1.2
        Complex_Types — G.1.1
        Discrete_Random — A.5.2
        Elementary_Functions — A.5.1
        Float_Random — A.5.2
        Generic_Complex_Arrays — G.3.2
        Generic_Complex_Elementary_Functions — G.1.2
        Generic_Complex_Types — G.1.1
        Generic_Elementary_Functions — A.5.1
        Generic_Real_Arrays — G.3.1
        Real_Arrays — G.3.1
    Real_Time — D.8
        Timing_Events — D.15
    Sequential_IO — A.8.1
    Storage_IO — A.9
    Streams — 13.13.1
        Storage_Streams — 13.13.1
            Bounded_FIFO_Streams — 13.13.1
            FIFO_Streams — 13.13.1
      Stream_IO — A.12.1
    Strings — A.4.1
        Bounded — A.4.4
            Equal_Case_Insensitive — A.4.10
            Hash — A.4.9
            Hash_Case_Insensitive — A.4.9
            Less_Case_Insensitive — A.4.10
        Equal_Case_Insensitive — A.4.10
        Fixed — A.4.3
            Equal_Case_Insensitive — A.4.10
            Hash — A.4.9
            Hash_Case_Insensitive — A.4.9
            Less_Case_Insensitive — A.4.10
        Hash — A.4.9
        Hash_Case_Insensitive — A.4.9
        Less_Case_Insensitive — A.4.10
        Maps — A.4.2
            Constants — A.4.6
        Text_Buffers — A.4.12
            Bounded — A.4.12
            Unbounded — A.4.12
        Unbounded — A.4.5
            Equal_Case_Insensitive — A.4.10
            Hash — A.4.9
            Hash_Case_Insensitive — A.4.9
            Less_Case_Insensitive — A.4.10
        UTF_Encoding — A.4.11
            Conversions — A.4.11
            Strings — A.4.11
            Wide_Strings — A.4.11
            Wide_Wide_Strings — A.4.11
        Wide_Bounded — A.4.7
            Wide_Equal_Case_Insensitive — A.4.7
            Wide_Hash — A.4.7
            Wide_Hash_Case_Insensitive — A.4.7
        Wide_Equal_Case_Insensitive — A.4.7
        Wide_Fixed — A.4.7
            Wide_Equal_Case_Insensitive — A.4.7
            Wide_Hash — A.4.7
            Wide_Hash_Case_Insensitive — A.4.7
        Wide_Hash — A.4.7
        Wide_Hash_Case_Insensitive — A.4.7
        Wide_Maps — A.4.7
            Wide_Constants — A.4.7
        Wide_Unbounded — A.4.7
            Wide_Equal_Case_Insensitive — A.4.7
            Wide_Hash — A.4.7
            Wide_Hash_Case_Insensitive — A.4.7
        Wide_Wide_Bounded — A.4.8
            Wide_Wide_Equal_Case_Insensitive — A.4.8
            Wide_Wide_Hash — A.4.8
            Wide_Wide_Hash_Case_Insensitive — A.4.8
        Wide_Wide_Equal_Case_Insensitive — A.4.8
        Wide_Wide_Fixed — A.4.8
            Wide_Wide_Equal_Case_Insensitive — A.4.8
            Wide_Wide_Hash — A.4.8
            Wide_Wide_Hash_Case_Insensitive — A.4.8
        Wide_Wide_Hash — A.4.8
        Wide_Wide_Hash_Case_Insensitive — A.4.8
        Wide_Wide_Maps — A.4.8
            Wide_Wide_Constants — A.4.8
        Wide_Wide_Unbounded — A.4.8
            Wide_Wide_Equal_Case_Insensitive — A.4.8
            Wide_Wide_Hash — A.4.8
            Wide_Wide_Hash_Case_Insensitive — A.4.8
    Synchronous_Barriers — D.10.1
    Synchronous_Task_Control — D.10
        EDF — D.10
    Tags — 3.9
        Generic_Dispatching_Constructor — 3.9
    Task_Attributes — C.7.2
    Task_Identification — C.7.1
    Task_Termination — C.7.3
    Text_IO — A.10.1
        Bounded_IO — A.10.11
        Complex_IO — G.1.3
        Editing — F.3.3
        Text_Streams — A.12.2
        Unbounded_IO — A.10.12
    Unchecked_Conversion — 13.9
    Unchecked_Deallocate_Subpool — 13.11.5
    Unchecked_Deallocation — 13.11.2
    Wide_Characters — A.3.1
        Handling — A.3.5
    Wide_Command_Line — A.15.1
    Wide_Directories — A.16.2
    Wide_Environment_Variables — A.17.1
    Wide_Text_IO — A.11
        Complex_IO — G.1.4
        Editing — F.3.4
        Text_Streams — A.12.3
        Wide_Bounded_IO — A.11
        Wide_Unbounded_IO — A.11
        Wide_Wide_Characters — A.3.1
        Handling — A.3.6
    Wide_Wide_Command_Line — A.15.1
    Wide_Wide_Directories — A.16.2
    Wide_Wide_Environment_Variables — A.17.1
    Wide_Wide_Text_IO — A.11
        Complex_IO — G.1.5
        Editing — F.3.5
        Text_Streams — A.12.4
        Wide_Wide_Bounded_IO — A.11
        Wide_Wide_Unbounded_IO — A.11
  Interfaces — B.2
      C — B.3
          Pointers — B.3.2
          Strings — B.3.1
      COBOL — B.4
      Fortran — B.5
  System — 13.7
      Address_To_Access_Conversions — 13.7.2
      Atomic_Operations — C.6.1
        Exchange — C.6.2
        Integer_Arithmetic — C.6.4
        Modular_Arithmetic — C.6.5
        Test_And_Set — C.6.3
    Machine_Code — 13.8
    Multiprocessors — D.16
        Dispatching_Domains — D.16.1
    RPC — E.5
    Storage_Elements — 13.7.1
    Storage_Pools — 13.11
        Subpools — 13.11.4

What’s Next?

Please, read the Wikibook or go through the materials at learn.adacore.com. There wasn’t much covered here, though we hope you found it useful.