Strong Typing, Objects, Constants & Named Numbers

Understand Ada's strong, name-equivalent type system, how to declare variables (objects) and constants, the difference between typed constants and named numbers, default initialization, and why distinct types for distinct quantities catch bugs at compile time.

In most languages a type mainly tells the compiler how many bytes to reserve. In Ada a type is a design tool: it states what values are meaningful, what operations make sense, and which other values it may never be confused with. Two types declared separately are different even if they have the same representation — Ada uses name equivalence — so you cannot accidentally add a distance in feet to a distance in meters. This lesson covers declaring objects and constants, named numbers, and the strong-typing rules that make Ada programs robust.

Objects: Variables and Constants

In Ada, variables and constants are both called objects. The declaration form is 'Name : Type := Initial_Value;' — for example Count : Integer := 0;. Several objects can be declared together (X, Y : Float := 0.0;). A constant adds the keyword constant and must be initialized: Max_Users : constant Integer := 100;. Constants cannot be modified after elaboration, and the compiler (with -gnatwa) even warns when a variable is never modified and could be a constant.

Scalar variables are not automatically initialized unless their type specifies a default (Ada 2012's Default_Value aspect) — reading an uninitialized scalar is a bounded error, and GNAT can detect it with the Initialize_Scalars / Normalize_Scalars pragmas and the 'Valid attribute. Always initialize objects at declaration when a sensible value exists; records and access objects have well-defined defaults (access objects start as null).

• Name : Type := Value; declares a variable (an object). • Name : constant Type := Value; declares a constant; initialization mandatory. • Scalars are not zero-initialized by default — initialize explicitly. • Access (pointer) objects default to null.

Named Numbers

A named number omits the type: Pi : constant := 3.141_59; or Buffer_Size : constant := 4_096;. It is evaluated at compile time with exact (arbitrary precision) arithmetic and is of a 'universal' type, so it can be used with any integer or real type without conversion. Named numbers are ideal for mathematical and configuration constants that must work with several numeric types.

• Named number: Name : constant := static_expression; (no type). • Universal_integer or universal_real, computed exactly at compile time. • Usable with any numeric type without conversion.

Strong Typing and Name Equivalence

'type Meters is new Float;' creates a brand-new type whose values and operations mirror Float but which is incompatible with Float and with every other type. 'type Feet is new Float;' is another distinct type. Assigning a Feet value to a Meters variable is a compile-time error. When a conversion is genuinely intended you write it explicitly — Meters (Wall) — which documents the intent and gives you the right place to apply a unit conversion factor.

This is the essence of Ada's strong typing: implicit conversions between numeric types never happen (not even Integer to Float), every mixed expression must be made explicit, and the compiler checks it. The result is that unit mistakes, index mix-ups (using an employee id as an array index of departments) and sign/width errors are caught before the program runs. Subtypes, covered later, add constraints without creating new types when you do want free mixing.

• Separately declared types are different even with identical representation. • No implicit numeric conversions — not even Integer → Float. • Explicit conversion: Target_Type (Expression). • Use distinct types for distinct physical quantities and ID kinds.

Code Examples

Meters and Feet are separate types. The commented-out assignment would not compile; instead Meters (Wall) * 0.3048 converts and scales explicitly. Pi is a named number and mixes with Float directly. Output: Wall height: 3.65760E+00 m Run length : 5.00000E+03 m Circle area r=2: 1.25664E+01 New wall : 1.30000E+01 ft

Trying to assign a Meters value to a Feet variable. The real GNAT diagnostic is: l4_err.adb:7:09: error: expected type "Feet" defined at line 3 l4_err.adb:7:09: error: found type "Meters" defined at line 2

with Ada.Text_IO; use Ada.Text_IO;

procedure L4_Types is
   type Meters is new Float;
   type Feet   is new Float;

   Pi         : constant := 3.141_59;     --  named number: no type, exact
   Run        : constant Meters := 5_000.0;
   Wall       : Feet := 12.0;
   Wall_In_M  : Meters;
begin
   --  Wall_In_M := Wall;             --  illegal: Feet is not Meters
   Wall_In_M := Meters (Wall) * 0.3048;   --  explicit conversion + scaling
   Put_Line ("Wall height:" & Meters'Image (Wall_In_M) & " m");
   Put_Line ("Run length :" & Meters'Image (Run) & " m");
   Put_Line ("Circle area r=2:" & Float'Image (Pi * 2.0 ** 2));
   Wall := Wall + 1.0;
   Put_Line ("New wall   :" & Feet'Image (Wall) & " ft");
end L4_Types;
procedure L4_Err is
   type Meters is new Float;
   type Feet   is new Float;
   A : constant Meters := 3.0;
   B : Feet;
begin
   B := A;          --  mixing two distinct types
   pragma Unreferenced (B);
end L4_Err;

Types as Walls Between Quantities

Derived types share representation but not identity; conversions are deliberate.

Total : Float := 0.0; Count : Integer := 3; ... Total := Total + Count;

Total := Total + Float (Count);

No implicit Integer-to-Float conversion exists; convert explicitly.

Limit : constant Integer;

Limit : constant Integer := 10;

A constant must be initialized in its declaration (deferred constants in package specs are the only exception).

Rate: Float;... Put_Line (Float'Image (Rate)); -- never assigned Rate: Float:= 0.0;

Uninitialized scalars hold unpredictable values; initialize at declaration.

Given 'type A is new Integer; type B is new Integer;', can you assign an A value to a B variable directly? A. Yes B. No, they are distinct types; conversion B (X) is required C. Only if values are small D. Only with a use clause

B. No — derived types are distinct (name equivalence); write B (X) to convert explicitly.

What is a named number? A. A variable with a label B. A typeless static constant computed exactly at compile time C. An enumeration value D. A numbered line

B. A typeless static constant (Name : constant := value;) of universal type, usable with any numeric type.

Does Ada convert Integer to Float implicitly in 'F := F + I'? A. Yes B. No — you must write Float (I) C. Only with -gnat2022 D. Only for constants

B. No — Ada never performs implicit numeric type conversions.

What value does an access (pointer) object have by default? A. Garbage B. 0 C. null D. It must be initialized

C. null — access objects are default-initialized to null.

Why does this program fail to compile, and what does GNAT report?

Real GNAT output: l4_err.adb:7:09: error: expected type "Feet" defined at line 3 l4_err.adb:7:09: error: found type "Meters" defined at line 2 Meters and Feet are distinct types; write B := Feet (A); (with any needed scale factor) to convert explicitly.

Float ──(new)──► Meters     Float ──(new)──► Feet
                      │                         │
                      └──── X := Y  ✗ ─────────┘  compile error
                      └── Meters (Y) * 0.3048 ✓ ──┘  explicit, documented
procedure L4_Err is
   type Meters is new Float;
   type Feet   is new Float;
   A : constant Meters := 3.0;
   B : Feet;
begin
   B := A;          --  mixing two distinct types
   pragma Unreferenced (B);
end L4_Err;