Ada for the curious

This post is for developers who are curious about Ada.

Contrary to some associations you might have (πŸ¦•?), Ada is a modern language, with modules, a strong type system, and easy-to-use concurrency. It’s blazing fast (πŸš€!), backed by a small but friendly community, and it’s easy to learn.

Instead of giving an extensive introduction to syntax you can easily look up yourself, I want to highlight some of the aspects that make Ada worth looking into. Consider it a teaser that gets you to know Ada enough to decide whether you like it or not, and provides pointers where to explore further if you do.

Let’s start with a countdown:

-- countdown.adb

with Ada.Text_IO; use Ada.Text_IO;

procedure Countdown is
begin
   for I in reverse 1 .. 10 loop
      Put_Line (Integer'Image (I));
   end loop;

   Put_Line ("Lift off!");
end Countdown;

Ada might look unfamiliar if you grew up with C-family languages, because its syntax was based on Pascal. But it’s a classical procedural language.

So what’s special about it?

What makes Ada different

The development of Ada was originally sponsored by the US Department of Defense, when they noticed they spend way too much money on building and fixing embedded systems. (Here's how I imagine it must have started.)History of Ada

Since the first version in 1983, the language has developed continuously and far beyond embedded systems, but the most important point to understand about Ada’s origin is that:

Ada was designed specifically for safety-critical software.

More than anything else, this has shaped who Ada is.

You notice it when looking at the main application areas of Ada: aircrafts, ships, railway control, medical devices, defense, space. But you also notice it as a programmer using Ada.

Ada wants you to produce correct programs. Therefore:

  • Ada wants to make it easy for you to properly engineer a solution.
  • Ada wants to make it easy for you to express your intent clearly - both towards the compiler and towards your fellow humans.
  • Ada wants to make it hard for you to make errors.

As a consequence, Ada is easy to understand and easy to get right.

I let you judge yourself. For me, personally, Ada feels very different in a subtle way. And that’s because the language is working with you, and not against you. It gently forces you to design when you code. And it provides safety nets everywhere.

Of course this comes at a cost. It means that Ada is not ideal for fast prototyping or for dynamic meta-programming. Ada was made to write well-designed, solid code, that works and that can still be read when coming back to it after years.

That’s where Ada shines.

But that doesn’t mean you can’t use it for small fun projects.

If you prefer designing over debugging, I bet you will enjoy Ada.

What makes Ada fun

Here are a few characteristics of Ada that many people like about it. This is subjective, of course, but it gives you a taste of why Ada might be worthwhile exloring.

The type system

Yes, we start the list of fun features with the type system. Because one of the nicest safety nets that Ada offers is range restrictions on the type level.

Let’s assume you deal with temperature readings. Instead of using a generic floating-point type and check whether the values you get are in your expected range, Ada lets you define a custom type as having floating-point values within a specific range - and the compiler and runtime check this range for you. For example:

type Temperature_Celcius is digits 9 range -273.15 .. 300_000_000.0;
type Temperature_Kelvin  is digits 9 range 0.0 .. 300_000_000.0;

Any values below -𝟸𝟽𝟹.𝟷𝟻 degrees Celcius or 0 Kelvin are not a valid temperature. (If you are not building a fusion reactor, your accepted range might be much smaller, of course.)

Or:

type Latitude  is new Float range 0.0 .. 360.0;
type Longitude is new Float range 0.0 .. 360.0;

This is not only incredibly handy, it also makes the range restrictions very clear to anyone working with your code.

Range restrictions in types are, in fact, helpful even if you don’t want to restrict the data range. Consider the following neat trick. You can declaring a custom floating-point type like this:

type Some_Float is new Float range Float'Range;

This means your derived type has the same range as Float, but excluding anything that is not in its range: NaN, infinity, or whatever non-numeric values your machine defines. So if your code ends up with something that is not a number, numeric operations raise a constraint error instead of propagating the non-numeric value through your whole program.

Array indices

One of the good ideas that Ada adopted from Pascal is the fact that array indices can come from any enumerable, bounded type. So it doesn’t matter whether you believe array indices should start with 0 or 1. You define how they start. And whether they use integers at all.

It actually means you get arrays and maps in one datatype, which feels very natural once you have it. When defining an array type, you simply specify the index type as well as the value type.

Here is an example where we use integers within a specific range as index:

type Position is range 100 .. 999;
type State is (Open, Closed, Unknown);

type Valves is array (Position) of State;

Example : Valves := (
   100    => Open,
   101    => Open,
   102    => Closed,
   103    => Open,
   others => Unknown
);

Attributes like 'First, 'Last, and 'Range make it possible to define loops in a way that’s hard to get wrong even if you change the underlying index range later.

for Pos in Position'First .. Position'Last loop
   ...
end loop;

-- This is equivalent to:
for Pos in Positions'Range loop
   ...
end loop;

Most importantly, arrays are memory-safe. The index type and range is part of the array, and both compiler and runtie check that all reads and writesare within the bounds of the array.

Separation of concerns

Like separating header and implementation in C, Ada separates packages into a specification (.ads) and a body (.adb), which tends to blend design and code. This approach is not specific to Ada, of course, but it emphasizes Ada’s focus on engineering software. Richard Riehle put it like this:

Ada as an engineering tool, requires the software developers to adopt an engineering attitude to using it. It is not enough to simply be a good computer programmer when human safety is at risk. Software at that level of risk must be engineered.

Here is an example. Assume we wanted to implement Conway’s Game of Life. We could start thinking about how to call it from a main procedure:

-- main.adb

with Game_Of_Life;
use  Game_Of_Life;

procedure Main is
begin

   Init_Board (
      Rows    => 800,
      Columns => 600,
      Pattern => Glider_Collision
   );

   Run;

end Main;

This defines what we need in the public part of our package specification:

-- game_of_life.ads

package Game_Of_Life is

    type Patterns is (
       Ants,
       Blinker,
       Dart,
       Fountain,
       Glider_Collision,
       Herschel_Climber,
       Spaceship
    );
    -- Predefined patterns, taken from the pattern catalogue at playgameoflife.com/lexicon.
    -- Their size is adapted to the board size.

    procedure Init_Board (Rows : Positive, Columns : Positive, Pattern : Patterns);
    -- Creates a game board with the specified number of rows and columns,
    -- and initializes the living cells according to the given start pattern.
    -- TODO: Requires a minimum size to fit the pattern.

    procedure Run;
    -- Runs the simulation.

private
   
    -- Specifications of all private types, functions, and procedures.

end Game_Of_Life;

If you have a specification file with proper comments, this can serve as a very helpful documentation of a library. (Check the .ads files in the gnatcoll-core repository for examples.)

Then we mirror the specification in the body, providing the actual implementation of the procedures. Doing so would usually inform which private specifications we need in the package specification.

-- game_of_life.adb

package body Game_Of_Life is

    procedure Init_Board (Rows : Positive, Columns : Positive) is
    begin
       -- Implementation left out.
       null;
    end Init_Board;

    procedure Load_Pattern (Start_Pattern : Pattern) is
    begin
       -- Implementation left out.
       null;
    end Load_Pattern;

    procedure Run is
    begin
       -- Implementation left out.
       null;
    end Run;

end Game_Of_Life;

Simplicity

There is a very nice interview with Niklaus Wirth (the Swiss computer scientist who invented Pascal), where he remembers that during his time, all existing programming languages were unneccessarily complex. He wanted to design a language that is as simple as possible without losing power.

In Ada, you see some of this legacy. One example are exceptions.

In Ada, exceptions are like objects, not types. You define an exception like this:

Timestamp_Is_In_The_Past : exception;

You raise it like this:

raise Timestamp_Is_In_The_Past;

Or, if you want to include more information, like this:

raise Timestamp_Is_In_The_Past with "Input timestamp cannot be in the past";

That’s it. For basic exceptions, this is arguably all you need. (With the exception of an error hierarchy maybe.)

Safe real-time programming

Concurrent and real-time programming are standard parts of Ada since its beginning. Their semantics is the same independent of whether you execute them on Linux, an RTOS, or on a bare metal target. And it’s as deterministic and safe as you can get. Read more about it.

The ecosystem and community

The Ada ecosystem can be a bit confusing in the beginning. But the core is simple: Ada is a free language. It’s an ISO standard and not owned by any company.

There are free and proprietary compilers. The one you will come across first and most often is called GNAT, a free Ada compiler integrated into GCC.

Ada has a small but friendly and welcoming community.

Forum: https://forum.ada-lang.io/

Discord: https://discord.gg/edM2czVKN4

Reddit: https://www.reddit.com/r/ada

There is also a monthly meetup, usually at the beginning of each month, which is accounced in the forum and on Discord.

Resources to learn Ada

Similar to this post, David Given has written an overview of Ada features called A random walk through Ada.

But if you want to get your hands wet instead, there are two entry points I would recommend.

AdaCore, a driving force behind many current developments in Ada, has a collection of excellent tutorials for people from different perspectives, which allow you to play with Ada directly in the browser. I highly recommend these tutorials as starting point.

The next great entry point, when you want to build and run Ada on your own machine, is ada-lang.io. It provides not only resources but also the package manager Alire, built by the community exactly for people like you.

Starting can be as easy as:

  • Download Alire.
  • Select the default toolchain.
  • Create a new repository.
  • Build and run it.

Once you want to know the language in more depth, there is no way around Programming in Ada 2012 by John Barnes, and the Ada Reference Manual (ARM) (or a prettier version of it on ada-lang.io). Both are very extensive and quite accessible, but feel free to not worry about them in the beginning - even though people will probably point you to both if you ask for pointers.

To explore the rest of the iceberg, Awesome Ada provides a pretty comprehensive list of resources.

If you want to dive into existing Ada code bases, here are a few suggestions:

For a still small but growing collection of notes on how to do what in Ada, see my Ada cookbook.

Are there jobs in Ada?

You can find some of the companies using Ada when checking the list of customers of AdaCore. This list is certainly not complete, but the picture is pretty representative.

Companies range from big names, like Thales, Airbus, and the Automotive Team at NVIDIA, to start-ups you probably never heard of, like Latence Tech.

Unfortunately, Ada jobs are often not heavily advertised. Even if you look at open positions at companies that hire Ada programmers, Ada might be mentioned as a nice-to-have experience, but it’s almost never in the job title.