Package Geometry is
Type Point is private;
Function Create( X, Y : Integer ) return Point;
Function X( Object : Point ) return Integer;
Function Y( Object : Point ) return Integer;
Procedure X( Object : in out Point; Value : Integer );
Procedure X( Object : in out Point; Value : Integer );
Private
Type Point is record
Data_X, Data_Y : Real:= 0.0;
end record;
Function X ( Object : Point ) return Integer is (Object.Data_X);
Function Y ( Object : Point ) return Integer is (Object.Data_X);
Function Create( X, Y : Integer ) return Point is
( Data_X => X, Data_Y => Y);
End Geometry;
(2) reuse, realized in Ada via generics. Generic
Type Item is private;
One : Item;
with "*"(Left, Right : Item) return Item is <>;
Function Generic_Exponent( Base : Item; Exponent : Natural ) return Item;
-- ...implementation
Function Generic_Exponent( Base : Item; Exponent : Natural ) return Item is
(case Exponent is
when 0 => One,
when 1 => Base,
when 2 => Base*Base,
when others =>
-- Rule: X**2k = (X**k)**2; X**2k+1 = (X**k)**2 * X.
(Generic_Exponent(Base, Generic_Exponent(Base, Exponent/2), 2)
* (if Exponent mod 2 = 1 then Base else One);
);
(3) inheritance, realized with type-cloning. Type Temperature is range -40..2_000;
Type Probe_Range is new Temperature range -20..200;
(4) abstract interfaces, without touching OOP, arguably generics. (See above; note how we're depending on the type, a "one value", and a "multiply" operation.)
(5) type extension, for extending types you finally have to buy into OOP. Type Base is null record;
Function Text(Object : Base) return String is ("[]");
Type Color is ( Red, Green, Blue, Black, White );
Type Colored_Thing is new Base with record
Color_Data : Color := Red;
end record;
Function Text(Object : Colored_Thing) return String is
('[' & Color'Image(Object.Color_Data) & " ]");
(6) dynamic dispatch. -- Gets text from user.
Function Prompt return String; -- def elsewhere.
-- Gets a previously saved object.
Function Get( Name : String ) return Base'Class; -- def elsewhere.
Unknown_From_User : Base'Class renames Get( Prompt );
--...
-- The following will print "[]" if it is BASE, and
-- "[ COLOR ]" when COLORED_THING, replacing 'COLOR' with
-- the textual representation/literal for the value.
Ada.Text_IO.Put_Line( Unknown_From_User.Text ); Ada.Text_IO.Create (
File => File,
Mode => Ada.Text_IO.Out_File,
Name => "test.txt",
Form => "shared=no"
);
The "maze of alternative package bodies and accompanying conditional compilation logic" is an artifact of C's approach to 'portability' using the preprocessor. Typically, the conditionality should be stable once you abstract it (using the compiler's project-management to select the correct body for a particular configuration) -- As a stupidly trivial example, consider the path separator, for the specification you could have: Package Dependency is
Package OS is
Function Separator return String;
End OS;
End Dependency;
-- ...
Package Dependency is
Package body OS is separate;
End Dependency;
-- Windows
separate (Dependency)
Package OS is
Function Separator return String is ("\");
End OS;
-- Classic Mac
separate (Dependency)
Package OS is
Function Separator return String is (":");
End OS;
-- VMS
separate (Dependency)
Package OS is
Function Separator return String is (".");
End OS;
-- UNIX-like
separate (Dependency)
Package OS is
Function Separator return String is ("/");
End OS;
Then in your the rest of your program, you program against the abstraction of DEPENDENCY.OS (and whatever other dependencies you have, likewise), and thus separate out the implementation dependency. Generic
Type Index is (<>); -- Any discrete type.
Type Element is limited private; -- Any non-discriminated type.
Type Vector is array(index range <>) of element; -- An array-type of Element, indexed by Index.
with Function "="(Left, Right: Element) return Boolean <>; -- Equal, defaulted.
with Function "<"(Left, Right: Element) return Boolean <>; -- Less-than, defaulted.
Function Generic_Sort( X : Vector ) return Vector;
Now when we instantiate we can inject '>' in place of the '<', reversing the direction of the sort: Function Sort is new Generic_Sort( Index => Integer, Element => Integer, Vector => Integer_Array, "<" => ">"); Function Example (X : Interfaces.Unsigned_16) return Boolean
with Import, Convention => COBOL, Link_Name => "xmpl16";
You can even put pre-/post-conditions on it. Subtype Numeric is String
with Dynamic_Predicate => (for all C of Numeric => C in '0'..'9'),
Predicate_Falure => raise Constraint_Error with "'" & Numeric &"' is not numeric.";
--...
Count : Numeric renames Get_User_Value;
--...
return Query("SELECT \* FROM Some_Table WHERE Count=" & Count & ";");
The above is perfectly safe because the constraint imposed prohibits the SQL-injection... and you can even enforce something like SQL_Escaping: PACKAGE Example IS
-- The only way to get a value of ESCAPED_STRING is via calling Create.
Type Escaped_String is private;
Function Create( X:String ) return Escaped_String;
PRIVATE
Type Escaped_String is new String;
Function Create( X:String ) return Escaped_String is
( SQL_Escape(X) );
END Example; Text : Constant String := Read_Chapter( Book );
Additionally, nesting DECLARE blocks and subprograms allows a fairly fine-tuned memory-usage/cleanup using the stack. The above example could, for example, be part of an outer DECLARE block, which has an inner DECLARE, perhaps with "Paragraphs : Constant String_Vector := Get_Paragraphs( Text );" in its declarative region and "For Paragraph of Paragraphs loop" in its body... as soon as the block is exited, the stack is popped, reclaiming the used memory. This, in turn, means that the need for heap allocation is greatly reduced.
You can even use unconstrained arrays to provide the same functionality that Optional does in functional-programming, provided the element-type can be an element of an array:
And there you have the mechanism for Optional; just use "For Object of Optional_Array Loop" to enclose your operations and bam, it works perfectly.