Get the code: learn.ada
Ada is a strong statically typed imperative, object-oriented, real-time, parallel and distributed programming language from the Pascal/Algol family of languages, but nowadays, it only has a passing resemblance to Pascal, with the only remnants left being the begin/end
keyword pair, the :=
assignment symbol, records and if/case
control statement structures.
Ada was originally designed to be an object-based language and to replace 100’s of languages in use by the US government. This means that all entities are objects, not in the object-oriented sense. The language became Object-Oriented in 1995, and added interfaces derived from Java in 2005. Contract based programming was introduced with Ada 2012.
Ada was designed to be easy to read and learn, even for non-programmers, e.g. management within an organisation, therefore programs written in the language tend to be a bit more verbose.
Ada is a modern programming language, and now has a package manager like other modern languages, Alire, see below.
-- Comments are written with a double hyphen and exist until the end of
-- the line.
-- You do not need to call the entry point "Main" or "main," you should
-- name it based on what the program does.
procedure Empty is
-- This is a declarative part.
begin
-- Statements go here.
null; -- Do nothing here.
end Empty;
-- Ada compilers accept compilation units which can be library packages,
-- tasks, sub-programs, generics, etc.
-- This is where "context clauses" go, these can be pragmas or "with"
-- statements. "with" is equivalent to "include" or "import" in other
-- languages.
with Ada.Text_IO; -- Get access to a library package.
procedure Hello is
begin
Ada.Text_IO.Put_Line ("Hello, world");
Ada.Text_IO.Put ("Hello again, world");
Ada.Text_IO.New_Line;
end Hello;
-- Ada has a real module system. Modules are called packages and are split into
-- two component parts, the specification and a body.
-- It is important to introduce packages early, as you will be using them from
-- the start.
package Stuff is
-- We could add the following line in order to tell the compiler that this
-- package does not have to run any code before the "main" procedure starts.
-- pragma Preelaborate;
-- Packages can be nested within the same file or externally.
-- Nested packages are accessed via dot notation, e.g. Stuff.Things.My.
package Things is
My : constant Integer := 100;
end Things;
-- If there are sub-programs declared within the specification, the body
-- of the sub-program must be declared within the package body.
procedure Do_Something; -- If a subprogram takes no parameters, empty
-- parentheses are not required, unlike other
-- languages.
-- We can also make generic sub-programs.
generic
type Element is (<>); -- The "(<>)" notation specifies that only
-- discrete types can be passed into the generic.
procedure Swap (Left, Right : in out Element);
-- Sometimes we want to hide how a type is defined from the outside world
-- so that nobody can mess with it directly. The full type must be defined
-- within the private section below.
type Blobs is private;
-- We can also make types "limited" by putting this keyword after the "is"
-- keyword, this means that the user cannot copy objects of that type
-- around, like they normally could.
private
type Blobs is new Integer range -25 .. 25;
end Stuff;
package body Stuff is
-- Sub-program body.
procedure Do_Something is
-- We can nest sub-programs too.
-- Parameters are defined with the direction of travel, in, in out, out.
-- If the direction of travel is not specified, they are in by default.
function Times_4 (Value : in Integer) return Integer is
begin
return Value * 4;
end Times_4;
I : Integer := 4;
begin
I := Times_4 (I);
end Do_Something;
-- Generic procedure body.
procedure Swap (Left, Right : in out Element) is
Temp : Element := Left;
begin
Left := Right;
Right := Temp;
end Swap;
begin
-- If we need to initialise something within the package, we can do it
-- here.
Do_Something;
end Stuff;
with Ada.Unchecked_Conversion;
with Ada.Text_IO;
with Stuff;
procedure LearnAdaInY is
-- Indentation is 3 spaces.
-- The most important feature in Ada is the type. Objects have types and an
-- object of one type cannot be assigned to an object of another type.
-- You can, and should, define your own types for the domain you are
-- modelling. But you can use the standard types to start with and then
-- replace them later with your own types, this could be called a form of
-- gradual typing.
-- The standard types would only really be a good starting point for binding
-- to other languages, like C. Ada is the only language with a standardised
-- way to bind with C, Fortran and COBOL! See the links in the References
-- section with more information on binding to these languages.
type Degrees is range 0 .. 360; -- This is a type. Its underlying
-- representation is an Integer.
type Hues is (Red, Green, Blue, Purple, Yellow); -- So is this. Here, we
-- are declaring an
-- Enumeration.
-- This is a modular type. They behave like Integers that automatically
-- wrap around. In this specific case, the range would be 0 .. 359.
-- If we added 1 to a variable containing the value 359, we would receive
-- back 0. They are very useful for arrays.
type Degrees_Wrap is mod 360;
-- You can restrict a type's range using a subtype, this makes them
-- compatible with each other, i.e. the subtype can be assigned to an
-- object of the type, as can be seen below.
subtype Primaries is Hues range Red .. Blue; -- This is a range.
-- You can define variables or constants like this:
-- Var_Name : Type := Value;
-- 10 is a universal integer. These universal numerics can be used with
-- any type which matches the base type.
Angle : Degrees := 10;
Value : Integer := 20;
-- New_Angle : Degrees := Value; -- Incompatible types won't compile.
-- New_Value : Integer := Angle;
Blue_Hue : Primaries := Blue; -- A variable.
Red_Hue : constant Primaries := Red; -- A constant.
Yellow_Hue : constant Hues := Yellow;
Colour_1 : constant Hues := Red_Hue;
-- Colour_2 : constant Primaries := Yellow_Hue; -- uncomment to compile.
-- You can force conversions, but then you are warned by the name of the
-- package that you may be doing something unsafe.
function Degrees_To_Int is new Ada.Unchecked_Conversion
(Source => Degrees, -- Line continuations are indented by 2 spaces.
Target => Integer);
New_Value_2 : Integer := Degrees_To_Int (Angle); -- Note, space before (.
-- GNAT is the GNU Ada Translator (compiler).
-- Ada has a style guide and GNAT will warn you to adhere to it, and has
-- option to check your style so that you can correct it so that all Ada
-- source looks consistent. However, the style can be customized.
-- Yes, you can even define your own floating and fixed point types, this
-- is a very rare and unique ability. "digits" refers to the minimum
-- digit precision that the type should support. "delta" is for fixed
-- point types and refers to the smallest change that the type will support.
type Real_Angles is digits 3 range 0.0 .. 360.0;
type Fixed_Angles is delta 0.01 digits 5 range 0.0 .. 360.0;
RA : constant Real_Angles := 36.45;
FA : constant Fixed_Angles := 360.0; -- ".0" in order to make it a Float.
-- You can have normal Latin 1 based strings by default.
Str : constant String := "This is a constant string";
-- When initialising from a string literal, the compiler knows the bounds,
-- so we don't have to define them.
-- Strings are arrays. Note how parentheses are used to access elements of
-- an array? This is mathematical notation and was used because square
-- brackets were not available on all keyboards at the time Ada was
-- created. Also, because an array can be seen as a function from a
-- mathematical perspective, so it made converting between arrays and
-- functions easier.
Char : constant Character := Str (Str'First); -- "'First" is a type
-- attribute.
-- Ada 2022 includes the use of [] for array initialisation when using
-- containers, which were added in Ada 2012.
-- Arrays are usually always defined as a type.
-- They can be any dimension.
type My_Array_1 is array (1 .. 4, 3 .. 7, -20 .. 20) of Integer;
-- Yes, unlike other languages, you can index arrays with other discrete
-- types such as enumerations and modular types or arbitrary ranges.
type Axes is (X, Y, Z);
-- You can define the array's range using the 'Range attribute from
-- another type.
type Vector is array (Axes'Range) of Float;
V1 : constant Vector := (0.0, 0.0, 1.0);
-- A record is the same as a structure in C, C++.
type Entities is record
Name : String (1 .. 10); -- Always starts at a positive value,
-- inclusive range.
Position : Vector;
end record;
-- In Ada, array bounds are immutable. You therefore have to provide a
-- string literal with a value for every character.
E1 : constant Entities := ("Blob ", (0.0, 0.0, 0.0));
-- An alternative is to use an array aggregate and assign a default value
-- to every element that wasn't previously assigned in this aggregate.
-- "others" is used to indicate anything else that has not been
-- explicitly initialized.
E2 : constant Entities := (('B', 'l', 'o', 'b', others => ' '),
(0.0, 0.0, 0.0));
-- There are dynamic length strings (see references section) available in
-- the standard library.
-- We can make an object be initialised to its default values with the box
-- notation, "<>". "others" is used to indicate anything else that has not
-- been explicitly initialized.
Null_Entity : constant Entities := (others => <>);
-- Object-orientation is accomplished via an extension of record syntax,
-- tagged records, see link above in first paragraph.
-- We can rename objects (aliases) to make readability a bit better.
package IO renames Ada.Text_IO;
begin
-- We can output enumerations as names.
IO.Put_Line ("Blue_Hue = " & -- & is the string concatenation operator.
Blue'Image); -- ' accesses attributes on objects.
-- The Image attribute converts a value to a string.
-- Ada 2022 has extended Image to custom types too.
-- Access this with -gnat2022 compiler flag.
IO.Put_Line ("Yellow_Hue = " &
-- We can use the type's attribute.
Primaries'Image (Yellow_Hue));
-- We can define local variables within a declare block, this can be made
-- more readable by giving it a label.
Enum_IO : declare
package Hue_IO is new IO.Enumeration_IO (Hues);
-- Using a package makes everything inside that package visible within
-- this block, it is good practice to only do this locally and not on
-- a whole package within the context clause.
use Hue_IO;
begin
-- We can print out the enumeration values too.
Put (Purple); -- Note we don't have to prefix the Put procedure with
-- Hue_IO.
IO.New_Line; -- We still need to prefix with IO here.
Put (Red_Hue);
IO.New_Line;
end Enum_IO;
-- Loops have a consistent form. "<form> loop ... end loop".
-- Where "form" can be "while" or "for" or missing as below, if
-- you place the "loop ... end loop;" construct on their own lines,
-- you can comment out or experiment with different loop constructs more
-- easily.
declare
Counter : Positive := Positive'First; -- This is 1.
begin
-- We can label loops so we can exit from them more easily if we need to.
Infinite :
loop
IO.Put_Line ("[Infinite loop] Counter = " & Counter'Image);
Counter := Counter + 1;
-- This next line implements a repeat ... until or do ... while loop construct.
-- Comment it out for an infinite loop.
exit Infinite when Counter = 5; -- Equality tests use a single "=".
end loop Infinite; -- Useful when implementing state machines.
end;
declare -- We don't have to have a label.
Counter : Positive := Positive'First; -- This is 1.
begin
while Counter < 10
loop
IO.Put_Line ("Counter = " & Counter'Image);
Counter := Counter + 1; -- There is no explicit inc/decrement.
-- Ada 2022 introduced @ for LHS, so the above would be written as
-- Counter := @ + 1; -- Try it, -gnat2022.
end loop;
end;
declare
package Hue_IO is new IO.Enumeration_IO (Hues);
-- We can have multiple packages on one line, but I tend to use one
-- package per line for readability.
use IO, Hue_IO;
begin
Put ("Hues : "); -- Note, no prefix.
-- Because we are using the 'Range attribute, the compiler knows it is
-- safe and can omit run-time checks here.
for Hue in Hues'Range
loop
Put (Hue);
-- Types and objects know about their bounds, their First .. Last
-- values. These can be specified as range types.
if Hue /= Hues'Last then -- The /= means "not equal to" like the
-- maths symbol ≠.
Put (", ");
end if;
end loop;
IO.New_Line;
end;
-- All objects know their bounds, including strings.
declare
C : Character := Str (50); -- Warning caused and exception raised at
-- runtime.
-- The exception raised above can only be handled by an outer scope,
-- see wikibook link below.
begin
null; -- We will never get to this point because of the above.
end;
exception
when Constraint_Error =>
IO.Put_Line ("Caught the exception");
end LearnAdaInY;
Now, that’s a lot of information for a basic intro to Ada, and I’ve only touched the surface, there’s much more to look at in the references section below. I haven’t even touched on dynamic memory allocation which includes pools, this is because for the most part, Ada programs don’t need it, you can do a lot without it.
As I stated above, Ada barely looks like Pascal and if you look at the original Green specification (Warning: Huge 4575 page scanned PDF - starting on page 460), it looks nothing like it at all (page 505 of that PDF).
The above source code will compile, but also will give warnings showing the power of the strong static type system.
If you already have the GNAT toolchain installed, you can cut and paste the above into a new file, e.g. learn-ada-in-y.ada
and then run the following:
$ gnatchop learn-ada-in-y.ada # This breaks the program into its specification ".ads" and body ".adb".
$ gnatmake empty.adb # gnatmake takes care of compilation of all units and linking.
$ gnatmake hello.adb
$ gnatmake learnadainy.adb
Or, download Alire, copy it to somewhere in your PATH and then do the following:
N.B. Alire will automatically install the toolchain for you if you don’t have one installed and will ask you to select which you want to use.
$ alr search learnadainy
$ alr get learnadainy
$ cd learnadainy
$ alr run empty
$ alr run hello
$ alr run learnadainy
Multi-line comments are not allowed as they are error prone.
Such comments would require a closing comment delimiter and this would again raise the dangers associated with the (unintentional) omission of the closing delimiter: entire sections of a program could be ignored by the compiler without the programmer realizing it
Got a suggestion? A correction, perhaps? Open an Issue on the GitHub Repo, or make a pull request yourself!
Originally contributed by Luke A. Guest, and updated by 4 contributors.