Formal Packages, Signature Packages & Formal Derived Types
Build signature packages that bundle a type with its required operations, pass whole instances as formal packages (with package M is new Sig (<>)), write generics over class hierarchies with formal derived/tagged types, and understand generic contract errors at instantiation.
As generics grow, passing a long list of types and operations becomes unwieldy. A signature package groups them: an empty generic package whose formal part describes a concept (a monoid: a type, an associative operation and a zero). Other generics then take any instance of that signature as a single formal package parameter. Formal derived types let a generic accept any descendant of a tagged type and use its components.
Signature Packages and Formal Packages
'generic type T is private; with function "+" (L, R : T) return T is <>; Zero : T; ... package Monoid is end Monoid;' declares a concept. Instances such as 'package Int_Sum is new Monoid (Integer, "+", 0, Integer'Image);' model it. A generic declares 'with package M is new Monoid (<>);' to accept any instance; inside, the actuals are accessed as M.T, M.Zero, M."+". The (<>) box means any actuals; you can also constrain some (with package M is new Monoid (T => Integer, others => <>)). Ada's container library uses formal packages in, for example, Ada.Containers.Generic_Sort-based designs and user-built algorithm libraries.
• Signature package: a generic with formals and an empty body. • with package M is new Sig (<>); — formal package parameter. • Access actuals via M.T, M.Op.
Formal Derived and Tagged Types
'type S is new Shape with private;' accepts Shape or any descendant; inside the generic, Shape's visible components (X.Size) and primitive operations are available, and the result keeps the actual type. 'type T is tagged private;' accepts any tagged type; 'type T is abstract tagged private' also abstract ones. 'type T is interface' formals exist too. This combines static generics with dynamic OOP (mixins: a generic that adds components to any type of a class).
• type S is new Parent with private; — any descendant of Parent. • type T is tagged private; — any tagged type. • Generic mixins add features to any type in a class.
Code Examples
Folding works for any Monoid instance: integers with + and 0 (1+2+3+4 = 10), characters with Max and Character'First ('z'). Doubled accepts any Shape descendant and returns a Box. GNAT notes that Monoid's formals are unused inside the (empty) signature package itself — expected for this idiom. Output: Int sum fold = 10 Max char fold = 'z' box of size 42 Vector length: 2
Instantiation is checked against the formal's contract. Real GNAT diagnostics: l40_err.adb:7:37: error: expect discrete type in instantiation of "Item" l40_err.adb:7:37: error: instantiation abandoned generic type T is private; with function "+"...; Zero: T; with function Image...; type T2 is private;... -- 12 formals repeated in every algorithm generic with package M is new Monoid (<>);
Group related formals in a signature package.
generic type S is tagged private; function Doubled (X: S) return S; -- then X.Size type S is new Shape with private;
To use Shape's components, the formal must be derived from Shape.
function Next_Float is new Next (Float); -- formal is (<>) -- use a formal 'digits <>' generic for floating types
Actuals must match the formal category (verified error).
What does 'with package M is new Monoid (<>);' accept? A. Only Monoid itself B. Any instance of Monoid C. Any package D. A child package
What is a signature package? A. A digitally signed package B. A generic that bundles a type with required operations, usually with an empty body C. A package with only constants D. The main program
Which formal accepts any descendant of Shape and allows access to Size? A. type S is private; B. type S is new Shape with private; C. type S is tagged private; D. type S is (<>);
What happens when instantiating 'type Item is (<>)' with Float? A. Works B. Compile error: expect discrete type C. Runtime error D. Float is converted Trace the formal package example.
Real output: Int sum fold = 10 Max char fold = 'z' box of size 42 Vector length: 2
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Vectors;
procedure L40_Formal_Pkg is
-- A "signature" package: bundles a type with the operations it must have
generic
type T is private;
with function "+" (L, R : T) return T is <>;
Zero : T;
with function Image (X : T) return String;
package Monoid is
end Monoid;
-- A generic that takes any instance of the signature (formal package)
generic
with package M is new Monoid (<>);
package Folding is
type T_Array is array (Positive range <>) of M.T;
function Fold (A : T_Array) return M.T;
function Show (A : T_Array) return String;
end Folding;
package body Folding is
use M;
function Fold (A : T_Array) return M.T is
Acc : M.T := Zero;
begin
for X of A loop
Acc := Acc + X;
end loop;
return Acc;
end Fold;
function Show (A : T_Array) return String is
("fold =" & Image (Fold (A)));
end Folding;
function Concat (L, R : Character) return Character is (Character'Max (L, R));
function Char_Image (C : Character) return String is (" '" & C & "'");
package Int_Sum is
…procedure L40_Err is
generic
type Item is (<>); -- requires a discrete type
function Next (X : Item) return Item;
function Next (X : Item) return Item is (Item'Succ (X));
function Next_Float is new Next (Float);
begin
null;
end L40_Err;