Ada: Records (Type System)

By | August 1, 2012

This is part of a series on programming in the Ada language, a mission critical programming language that I am attempting to learn and become proficient in.

This is part 2 of the sub-series on types in Ada. A second variety of types in Ada (the first being the enumeration) is the record. When I first learned C a few years back, records (called structures in that language) were what I always associated with user-defined types. They are effectively sets of other types which can be grouped together and incorporated into a single variable (ie. coordinates).

A simple record type is written like this:

type Coord is
   record
      Longitude : Float range -180.0 .. 180.0;
      Latitude  : Float range -90.0 .. 90.0;
   end record;

(Note the use of boundaries — Float is effectively an enumeration so it can be bounded)

Sometimes, it may also be helpful to have default values for each field.

type Coord is
   record
      Longitude : Float range -180.0 .. 180.0 := -22.5;
      Latitude  : Float range -90.0 .. 90.0   := 89.5;
   end record;

In Ada, records can be extended into other types with additional fields if they are declared as “tagged”. For example, if the longitude and latitude data isn’t enough and a new type that includes elevation is needed, it’s as simple as:

type Coord is tagged -- Coord has been changed to a tagged type
   record
      Longitude : Float range -180.0 .. 180.0;
      Latitude  : Float range -90.0 .. 90.0;
   end record;

type Position is new Coord with
   record
      Elevation : Float;
   end record;

The above declaration of “Position” is equivalent to:

type Position is
   record
      Longitude : Float range -180.0 .. 180.0;
      Latitude  : Float range -90.0 .. 90.0;
      Elevation : Float;
   end record;

There is no such thing as a “class” in Ada like there is in Java and C++. Instead, tagged records can be used to do the equivalent with its ability to be added on much as classes can be extended in Java and C++.

There are also discriminated records — records which work a lot like generics in other languages (as well as in Ada itself).

type Disc_Coord (Min_Lon, Max_Lon, Min_Lat, Max_Lat : Float) is
   record
      Longitude : Float range Min_Lon .. Max_Lon;
      Latitude  : Float range Min_Lat .. Max_Lat;
   end record;

To use such records, the discriminants of the type must be specified when creating an object of the type.

My_Coord : Disc_Coord (Min_Lon => -180.0, Max_Lon => 180.0, Min_Lat => -90.0, Max_Lat => 90.0);

Note that I’ve named the discriminants (Min_Lon, Max_Lon, …). This isn’t necessary — it’s possible to do just:

My_Coord : Disc_Coord (-180.0, 180.0, -90.0, 90.0);

However, that is a lot less readable to someone looking over the code. It’s also far more prone to errors — if you screw up the order and you’ve named the discriminants, you’re okay, but if you screw up the order and you don’t name the discriminants, you’re just plain screwed. Naming is also supported with procedure/function calls where you name the parameters.

A Quick Note on Naming
I have used parameters in the wrong order on more than one occasion with function calls in other languages that don’t support naming. Each occasion cost me a good hour’s worth of debugging and trying to figure out where my problem is. As a result, I find naming to be a very welcome feature.

Naming can and should also be when assigning values to objects that are record types: (note that this uses the none-discriminated records)

My_Coord_1 : Coord := (Longitude => -22.5, Latitude => 89.5);
My_Coord_2 : Coord := (-22.5, 89.5); -- equivalent to above

Note how unreadable the latter version is. You would have to pull up the specification of Coord to see what each number means. Is -22.5 the longitude or the latitude? A look at the specification says longitude. However, a look at the first version immediately says which is which — without having to look up the specification.

Getting back to discriminate types, it’s also possible to have one type be completely different types depending on the discriminate. Confused? The code should explain better than I can. These, by the way, are called variant records:

type Disc_Type (Version : Natural) is
   record
      case Version is
         when 1 =>
            Field_1 : Float;
         when 2, 3 =>
            Field_2 : Float;
         when others =>
            Field_1 : Float := 10;
      end case;
   end record;

The case statement uses the same format as case statements in procedure and function bodies (and does the same thing). It checks the discriminant and sends it to the correct when statement depending on its value.

Unlike many other languages, the case statement (or switch/case) does not require a break statement at the bottom. The break statement (inside switch statements) is one of my most common errors I make with languages that require it. Even after working with perhaps hundreds of switch/case statements in C, I still frequently forget to add the break at the end. The result is that execution simply continues to the next option without giving me an error. It’s usually months later, long after the code has been released when the problem is discovered. My guess is that this is the reason why switch statements are considered “evil” in such languages.

So far, of these, I plan to make use of standard, tagged and variant records in my astronomical calculations library. I don’t see anything that plain discriminated records could be used for… yet.

Coming next should be type conversions. Ada seems to be particular strict on these as it’s part of what makes it mission-critical.

4 thoughts on “Ada: Records (Type System)

  1. Fred

    You mentionned that type can be entended by using the tagged keyword.
    In fact, this is not really true. By using the tagged keyword, you allow to use dynamic dispatching on your new type and thus, provide an object orientation to your design (see this Wikibooks chapter) .
    But, it’s possible just to declare a new record extending another one without the need of tagged making it a Derived type (In ARM</a).
    This last form allows you to describe new type where you don't need any polymorphism behaviour.

    Reply
  2. stevie

    Plain discriminated records are for cases where an array in a record is required (i.e a matrix), but the bounds of the matrix are now known until runtime. Because it is impossible for an non-bounded array type to be declared without a range in record types a discriminate record is needed.

    —– The assumed way
    type My_Record is record
    This_Is_Illegal : String; — Compiler error
    end record;

    —- The C way
    type String_Access is access all String;
    function Free is new Ada.Unchecked_Deallocation(String_Access);
    type My_Dumb_Record is record
    This_Is_Dumb : String_Access; — Will have to be freed manually via
    — ada.unchecked_deallocation
    end record;

    Dumb.This_Is_Dumb := new String(1..3);
    Do_Something(Dumb);
    Free(Dumb.This_Is_Dumb);

    ——– Correct
    type My_Correct_Record(The_Size : Positive) is record
    This_Is_Legal : String(1..The_Size);
    end record;

    declare
    Correct : My_Correct_Record(3);
    begin
    Do_Something(Correct);
    end;

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *