Intro To Ada
Intro To Ada
Release 2025-05
Raphaël Amiard
and Gustavo A. Hoffmann
1 Introduction 3
1.1 History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Ada today . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Philosophy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 SPARK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Imperative language 5
2.1 Hello world . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Imperative language - If/Then/Else . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Imperative language - Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3.1 For loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3.2 Bare loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.3 While loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4 Imperative language - Case statement . . . . . . . . . . . . . . . . . . . . . . . . 12
2.5 Imperative language - Declarative regions . . . . . . . . . . . . . . . . . . . . . . 14
2.6 Imperative language - conditional expressions . . . . . . . . . . . . . . . . . . . 16
2.6.1 If expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.6.2 Case expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3 Subprograms 19
3.1 Subprograms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.1 Subprogram calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.1.2 Nested subprograms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.1.3 Function calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2 Parameter modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.3 Subprogram calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.3.1 In parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.3.2 In out parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3.3 Out parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3.4 Forward declaration of subprograms . . . . . . . . . . . . . . . . . . . . . 27
3.4 Renaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4 Modular programming 31
4.1 Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2 Using a package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.3 Package body . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.4 Child packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.4.1 Child of a child package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.4.2 Multiple children . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.4.3 Visibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.5 Renaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
i
5.2.1 Operational semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.3 Unsigned types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.4 Enumerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.5 Floating-point types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.5.1 Basic properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.5.2 Precision of floating-point types . . . . . . . . . . . . . . . . . . . . . . . . 49
5.5.3 Range of floating-point types . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.6 Strong typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.7 Derived types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.8 Subtypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.8.1 Subtypes as type aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6 Records 61
6.1 Record type declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.2 Aggregates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.3 Component selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.4 Renaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7 Arrays 67
7.1 Array type declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.2 Indexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.3 Simpler array declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.4 Range attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
7.5 Unconstrained arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.6 Predefined array type: String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.7 Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.8 Returning unconstrained arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7.9 Declaring arrays (2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
7.10 Array slices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
7.11 Renaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
12 Privacy 109
12.1 Basic encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
12.2 Abstract data types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
12.3 Limited types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
12.4 Child packages & privacy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
13 Generics 119
ii
13.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
13.2 Formal type declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
13.3 Formal object declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
13.4 Generic body definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
13.5 Generic instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
13.6 Generic packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
13.7 Formal subprograms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
13.8 Example: I/O instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
13.9 Example: ADTs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
13.10Example: Swap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
13.11Example: Reversing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
13.12Example: Test application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
14 Exceptions 139
14.1 Exception declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
14.2 Raising an exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
14.3 Handling an exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
14.4 Predefined exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
15 Tasking 143
15.1 Tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
15.1.1 Simple task . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
15.1.2 Simple synchronization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
15.1.3 Delay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
15.1.4 Synchronization: rendezvous . . . . . . . . . . . . . . . . . . . . . . . . . 147
15.1.5 Select loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
15.1.6 Cycling tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
15.2 Protected objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
15.2.1 Simple object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
15.2.2 Entries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
15.3 Task and protected types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
15.3.1 Task types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
15.3.2 Protected types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
iii
19 Standard library: Containers 199
19.1 Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
19.1.1 Instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
19.1.2 Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
19.1.3 Appending and prepending elements . . . . . . . . . . . . . . . . . . . . . 200
19.1.4 Accessing first and last elements . . . . . . . . . . . . . . . . . . . . . . . 202
19.1.5 Iterating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
19.1.6 Finding and changing elements . . . . . . . . . . . . . . . . . . . . . . . . 207
19.1.7 Inserting elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
19.1.8 Removing elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
19.1.9 Other Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
19.2 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
19.2.1 Initialization and iteration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
19.2.2 Operations on elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
19.2.3 Other Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
19.3 Indefinite maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
19.3.1 Hashed maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
19.3.2 Ordered maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
19.3.3 Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
24 Appendices 265
24.1 Appendix A: Generic Formal Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
24.1.1 Indefinite version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
24.2 Appendix B: Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
iv
Introduction to Ada
This course will teach you the basics of the Ada programming language and is intended for
those who already have a basic understanding of programming techniques. You will learn
how to apply those techniques to programming in Ada.
This document was written by Raphaël Amiard and Gustavo A. Hoffmann, with review from
Richard Kenner.
® Note
The code examples in this course use a 50-column limit, which greatly improves the
readability of the code on devices with a small screen size. This constraint, however,
leads to an unusual coding style. For instance, instead of calling Put_Line in a single
line, we have this:
Put_Line
(" is in the northeast quadrant");
or this:
Put_Line (" (X => "
& Integer'Image (P.X)
& ")");
Note that typical Ada code uses a limit of at least 79 columns. Therefore, please don't
take the coding style from this course as a reference!
® Note
Each code example from this book has an associated "code block metadata", which
contains the name of the "project" and an MD5 hash value. This information is used to
identify a single code example.
You can find all code examples in a zip file, which you can download from the learn
website2 . The directory structure in the zip file is based on the code block metadata.
For example, if you're searching for a code example with this metadata:
• Project: Courses.Intro_To_Ada.Imperative_Language.Greet
• MD5: cba89a34b87c9dfa71533d982d05e6ab
you will find it in this directory:
1 http://creativecommons.org/licenses/by-sa/4.0
CONTENTS: 1
Introduction to Ada
projects/Courses/Intro_To_Ada/Imperative_Language/Greet/
cba89a34b87c9dfa71533d982d05e6ab/
In order to use this code example, just follow these steps:
1. Unpack the zip file;
2. Go to target directory;
3. Start GNAT Studio on this directory;
4. Build (or compile) the project;
5. Run the application (if a main procedure is available in the project).
2 https://learn.adacore.com/zip/learning-ada_code.zip
2 CONTENTS:
CHAPTER
ONE
INTRODUCTION
1.1 History
In the 1970s the United States Department of Defense (DOD) suffered from an explosion
of the number of programming languages, with different projects using different and non-
standard dialects or language subsets / supersets. The DOD decided to solve this problem
by issuing a request for proposals for a common, modern programming language. The
winning proposal was one submitted by Jean Ichbiah from CII Honeywell-Bull.
The first Ada standard was issued in 1983; it was subsequently revised and enhanced in
1995, 2005 and 2012, with each revision bringing useful new features.
This tutorial will focus on Ada 2012 as a whole, rather than teaching different versions of
the language.
3
Introduction to Ada
In terms of modern languages, the closest in terms of targets and level of abstraction are
probably C++6 and Rust7 .
1.3 Philosophy
Ada's philosophy is different from most other languages. Underlying Ada's design are prin-
ciples that include the following:
• Readability is more important than conciseness. Syntactically this shows through the
fact that keywords are preferred to symbols, that no keyword is an abbreviation, etc.
• Very strong typing. It is very easy to introduce new types in Ada, with the benefit of
preventing data usage errors.
– It is similar to many functional languages in that regard, except that the program-
mer has to be much more explicit about typing in Ada, because there is almost no
type inference.
• Explicit is better than implicit. Although this is a Python8 commandment, Ada takes it
way further than any language we know of:
– There is mostly no structural typing, and most types need to be explicitly named
by the programmer.
– As previously said, there is mostly no type inference.
– Semantics are very well defined, and undefined behavior is limited to an absolute
minimum.
– The programmer can generally give a lot of information about what their program
means to the compiler (and other programmers). This allows the compiler to be
extremely helpful (read: strict) with the programmer.
During this course, we will explain the individual language features that are building blocks
for that philosophy.
1.4 SPARK
While this class is solely about the Ada language, it is worth mentioning that another lan-
guage, extremely close to and interoperable with Ada, exists: the SPARK language.
SPARK is a subset of Ada, designed so that the code written in SPARK is amenable to auto-
matic proof. This provides a level of assurance with regard to the correctness of your code
that is much higher than with a regular programming language.
There is a dedicated course for the SPARK language but keep in mind that every time we
speak about the specification power of Ada during this course, it is power that you can lever-
age in SPARK to help proving the correctness of program properties ranging from absence
of run-time errors to compliance with formally specified functional requirements.
6 https://en.wikipedia.org/wiki/C%2B%2B
7 https://www.rust-lang.org/en-US/
8 https://www.python.org
4 Chapter 1. Introduction
CHAPTER
TWO
IMPERATIVE LANGUAGE
Ada is a multi-paradigm language with support for object orientation and some elements of
functional programming, but its core is a simple, coherent procedural/imperative language
akin to C or Pascal.
® In other languages
One important distinction between Ada and a language like C is that statements and
expressions are very clearly distinguished. In Ada, if you try to use an expression where
a statement is required then your program will fail to compile. This rule supports a useful
stylistic principle: expressions are intended to deliver values, not to have side effects.
It can also prevent some programming errors, such as mistakenly using the equality
operator = instead of the assignment operation := in an assignment statement.
Listing 1: greet.adb
1 with Ada.Text_IO;
2
3 procedure Greet is
4 begin
5 -- Print "Hello, World!" to the screen
6 Ada.Text_IO.Put_Line ("Hello, World!");
7 end Greet;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet
MD5: cba89a34b87c9dfa71533d982d05e6ab
Runtime output
Hello, World!
5
Introduction to Ada
module, the Ada.Text_IO package, which contains a procedure to print text on the
screen: Put_Line.
• Greet is a procedure, and the main entry point for our first program. Unlike in C
or C++, it can be named anything you prefer. The builder will determine the entry
point. In our simple example, gprbuild, GNAT's builder, will use the file you passed
as parameter.
• Put_Line is a procedure, just like Greet, except it is declared in the Ada.Text_IO
module. It is the Ada equivalent of C's printf.
• Comments start with -- and go to the end of the line. There is no multi-line comment
syntax, that is, it is not possible to start a comment in one line and continue it in the
next line. The only way to create multiple lines of comments in Ada is by using -- on
each line. For example:
® In other languages
Procedures are similar to functions in C or C++ that return void. We'll see later how to
declare functions in Ada.
Listing 2: greet.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet is
4 begin
5 -- Print "Hello, World!" to the screen
6 Put_Line ("Hello, World!");
7 end Greet;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet_2
MD5: a58a1193207df44aa6edaa4fe1c14280
Runtime output
Hello, World!
This version utilizes an Ada feature known as a use clause, which has the form use package-
name. As illustrated by the call on Put_Line, the effect is that entities from the named
package can be referenced directly, without the package-name. prefix.
Listing 3: check_positive.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
3
4 procedure Check_Positive is
5 N : Integer;
6 begin
7 -- Put a String
8 Put ("Enter an integer value: ");
9
13 if N > 0 then
14 -- Put an Integer
15 Put (N);
16 Put_Line (" is a positive number");
17 end if;
18 end Check_Positive;
Project: Courses.Intro_To_Ada.Imperative_Language.Check_Positive
MD5: 2e8b4b2f3f258fd9e02c2d65846af101
The if statement minimally consists of the reserved word if, a condition (which must be
a Boolean value), the reserved word then and a non-empty sequence of statements (the
then part) which is executed if the condition evaluates to True, and a terminating end if.
This example declares an integer variable N, prompts the user for an integer, checks if the
value is positive and, if so, displays the integer's value followed by the string " is a positive
number". If the value is not positive, the procedure does not display any output.
The type Integer is a predefined signed type, and its range depends on the computer ar-
chitecture. On typical current processors Integer is 32-bit signed.
The example illustrates some of the basic functionality for integer input-output. The rel-
evant subprograms are in the predefined package Ada.Integer_Text_IO and include the
Get procedure (which reads in a number from the keyboard) and the Put procedure (which
displays an integer value).
Here's a slight variation on the example, which illustrates an if statement with an else
part:
Listing 4: check_positive.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
3
4 procedure Check_Positive is
5 N : Integer;
6 begin
7 -- Put a String
8 Put ("Enter an integer value: ");
9
13 -- Put an Integer
14 Put (N);
15
Project: Courses.Intro_To_Ada.Imperative_Language.Check_Positive_2
MD5: 28fca0d7840d06d478e5933e8182d1db
In this example, if the input value is not positive then the program displays the value fol-
lowed by the String " is not a positive number".
Our final variation illustrates an if statement with elsif sections:
Listing 5: check_direction.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
3
4 procedure Check_Direction is
5 N : Integer;
6 begin
7 Put ("Enter an integer value: ");
8 Get (N);
9 Put (N);
10
11 if N = 0 or N = 360 then
12 Put_Line (" is due north");
13 elsif N in 1 .. 89 then
14 Put_Line (" is in the northeast quadrant");
15 elsif N = 90 then
16 Put_Line (" is due east");
17 elsif N in 91 .. 179 then
18 Put_Line (" is in the southeast quadrant");
19 elsif N = 180 then
20 Put_Line (" is due south");
21 elsif N in 181 .. 269 then
22 Put_Line (" is in the southwest quadrant");
23 elsif N = 270 then
24 Put_Line (" is due west");
25 elsif N in 271 .. 359 then
26 Put_Line (" is in the northwest quadrant");
27 else
28 Put_Line (" is not in the range 0..360");
29 end if;
30 end Check_Direction;
Project: Courses.Intro_To_Ada.Imperative_Language.Check_Direction
MD5: 7759d30c9bb0bfb88efdf12128f9c382
This example expects the user to input an integer between 0 and 360 inclusive, and displays
which quadrant or axis the value corresponds to. The in operator in Ada tests whether a
scalar value is within a specified range and returns a Boolean result. The effect of the
program should be self-explanatory; later we'll see an alternative and more efficient style
to accomplish the same effect, through a case statement.
Ada's elsif keyword differs from C or C++, where nested else .. if blocks would be
used instead. And another difference is the presence of the end if in Ada, which avoids
the problem known as the "dangling else".
Listing 6: greet_5a.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet_5a is
4 begin
5 for I in 1 .. 5 loop
6 -- Put_Line is a procedure call
7 Put_Line ("Hello, World!"
8 & Integer'Image (I));
9 -- ^ Procedure parameter
10 end loop;
11 end Greet_5a;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet_5a
MD5: 7f588b67947126f789333adfaaf1b638
Runtime output
Hello, World! 1
Hello, World! 2
Hello, World! 3
Hello, World! 4
Hello, World! 5
Listing 7: greet_5a_reverse.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet_5a_Reverse is
4 begin
5 for I in reverse 1 .. 5 loop
6 Put_Line ("Hello, World!"
7 & Integer'Image (I));
8 end loop;
9 end Greet_5a_Reverse;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet_5a_Reverse
MD5: a0d5dcfc471fb1a107477c934fa527c2
Runtime output
Hello, World! 5
Hello, World! 4
Hello, World! 3
Hello, World! 2
Hello, World! 1
The bounds of a for loop may be computed at run-time; they are evaluated once, before
the loop body is executed. If the value of the upper bound is less than the value of the
lower bound, then the loop is not executed at all. This is the case also for reverse loops.
Thus no output is produced in the following example:
Listing 8: greet_no_op.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet_No_Op is
4 begin
5 for I in reverse 5 .. 1 loop
6 Put_Line ("Hello, World!"
7 & Integer'Image (I));
8 end loop;
9 end Greet_No_Op;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet_No_Op
MD5: 5070693fb0324d3e4e43a8c8c4f046e1
Build output
greet_no_op.adb:5:23: warning: loop range is null, loop will not execute [enabled␣
↪by default]
The for loop is more general than what we illustrated here; more on that later.
Listing 9: greet_5b.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet_5b is
4 -- Variable declaration:
5 I : Integer := 1;
6 -- ^ Type
7 -- ^ Initial value
8 begin
9 loop
10 Put_Line ("Hello, World!"
11 & Integer'Image (I));
12
13 -- Exit statement:
14 exit when I = 5;
15 -- ^ Boolean condition
16
17 -- Assignment:
18 I := I + 1;
19 -- There is no I++ short form to
20 -- increment a variable
21 end loop;
22 end Greet_5b;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet_5b
MD5: 5b218a64a07f64bd97774b574883c44a
Runtime output
Hello, World! 1
Hello, World! 2
Hello, World! 3
Hello, World! 4
Hello, World! 5
3 procedure Greet_5c is
4 I : Integer := 1;
5 begin
6 -- Condition must be a Boolean value
7 -- (no Integers).
8 -- Operator "<=" returns a Boolean
9 while I <= 5 loop
10 Put_Line ("Hello, World!"
11 & Integer'Image (I));
12
13 I := I + 1;
14 end loop;
15 end Greet_5c;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet_5c
MD5: 5d1d099477795b226db43736c2810274
Runtime output
Hello, World! 1
Hello, World! 2
Hello, World! 3
Hello, World! 4
Hello, World! 5
The condition is evaluated before each iteration. If the result is false, then the loop is
terminated.
This program has the same effect as the previous examples.
® In other languages
Note that Ada has different semantics than C-based languages with respect to the con-
dition in a while loop. In Ada the condition has to be a Boolean value or the compiler
will reject the program; the condition is not an integer that is treated as either True or
False depending on whether it is non-zero or zero.
4 procedure Check_Direction is
5 N : Integer;
6 begin
(continues on next page)
12 case N is
13 when 0 | 360 =>
14 Put_Line
15 (" is due north");
16 when 1 .. 89 =>
17 Put_Line
18 (" is in the northeast quadrant");
19 when 90 =>
20 Put_Line
21 (" is due east");
22 when 91 .. 179 =>
23 Put_Line
24 (" is in the southeast quadrant");
25 when 180 =>
26 Put_Line
27 (" is due south");
28 when 181 .. 269 =>
29 Put_Line
30 (" is in the southwest quadrant");
31 when 270 =>
32 Put_Line
33 (" is due west");
34 when 271 .. 359 =>
35 Put_Line
36 (" is in the northwest quadrant");
37 when others =>
38 Put_Line
39 (" Au revoir");
40 exit;
41 end case;
42 end loop;
43 end Check_Direction;
Project: Courses.Intro_To_Ada.Imperative_Language.Check_Direction_2
MD5: 1c758b76a2c3991cb4e2a0cf5e172ac3
This program repeatedly prompts for an integer value and then, if the value is in the range
0 .. 360, displays the associated quadrant or axis. If the value is an Integer outside this
range, the loop (and the program) terminate after outputting a farewell message.
The effect of the case statement is similar to the if statement in an earlier example, but the
case statement can be more efficient because it does not involve multiple range tests.
Notable points about Ada's case statement:
• The case expression (here the variable N) must be of a discrete type, i.e. either an
integer type or an enumeration type. Discrete types will be covered in more detail
later discrete types (page 43).
• Every possible value for the case expression needs to be covered by a unique branch
of the case statement. This will be checked at compile time.
• A branch can specify a single value, such as 0; a range of values, such as 1 .. 89; or
any combination of the two (separated by a |).
• As a special case, an optional final branch can specify others, which covers all values
3 procedure Main is
4 X : Integer;
5 begin
6 X := 0;
7 Put_Line ("The initial value of X is "
8 & Integer'Image (X));
9
Project: Courses.Intro_To_Ada.Imperative_Language.Variable_Declaration
MD5: cbb08d5e382fbfcc28e986bea80cd253
Runtime output
3 procedure Main is
4 procedure Nested is
5 begin
(continues on next page)
Project: Courses.Intro_To_Ada.Imperative_Language.Nested_Procedure
MD5: 2e7fb267e31232196065febd5e35e6ef
Runtime output
Hello World
A declaration cannot appear as a statement. If you need to declare a local variable amidst
the statements, you can introduce a new declarative region with a block statement:
3 procedure Greet is
4 begin
5 loop
6 Put_Line ("Please enter your name: ");
7
8 declare
9 Name : String := Get_Line;
10 -- ^ Call to the
11 -- Get_Line function
12 begin
13 exit when Name = "";
14 Put_Line ("Hi " & Name & "!");
15 end;
16
20 Put_Line ("Bye!");
21 end Greet;
Project: Courses.Intro_To_Ada.Imperative_Language.Greet_6
MD5: a9c0c14a1b3e2ebe07cd88f442787e3a
Á Attention
The Get_Line function allows you to receive input from the user, and get the result as
a string. It is more or less equivalent to the scanf C function.
It returns a String, which, as we will see later, is an Unconstrained array type (page 74).
For now we simply note that, if you wish to declare a String variable and do not know
its size in advance, then you need to initialize the variable during its declaration.
2.6.1 If expressions
Here's an alternative version of an example we saw earlier; the if statement has been
replaced by an if expression:
4 procedure Check_Positive is
5 N : Integer;
6 begin
7 Put ("Enter an integer value: ");
8 Get (N);
9 Put (N);
10
11 declare
12 S : constant String :=
13 (if N > 0
14 then " is a positive number"
15 else " is not a positive number");
16 begin
17 Put_Line (S);
18 end;
19 end Check_Positive;
Project: Courses.Intro_To_Ada.Imperative_Language.Check_Positive
MD5: 01f23463b14774f750dbb21f6c65ea09
The if expression evaluates to one of the two Strings depending on N, and assigns that
value to the local variable S.
Ada's if expressions are similar to if statements. However, there are a few differences
that stem from the fact that it is an expression:
• All branches' expressions must be of the same type
• It must be surrounded by parentheses if the surrounding expression does not already
contain them
• An else branch is mandatory unless the expression following then has a Boolean
value. In that case an else branch is optional and, if not present, defaults to else
True.
Here's another example:
3 procedure Main is
4 begin
5 for I in 1 .. 10 loop
6 Put_Line (if I mod 2 = 0
7 then "Even"
(continues on next page)
Project: Courses.Intro_To_Ada.Imperative_Language.Even_Odd
MD5: c89c3233ab8822c828f7a7bba8fd3f1c
Runtime output
Odd
Even
Odd
Even
Odd
Even
Odd
Even
Odd
Even
This program produces 10 lines of output, alternating between "Odd" and "Even".
3 procedure Main is
4 begin
5 for I in 1 .. 10 loop
6 Put_Line
7 (case I is
8 when 1 | 3 | 5 | 7 | 9 => "Odd",
9 when 2 | 4 | 6 | 8 | 10 => "Even");
10 end loop;
11 end Main;
Project: Courses.Intro_To_Ada.Imperative_Language.Case_Expression
MD5: 6ce40efc987c2665960b1f08d30d780d
Runtime output
Odd
Even
Odd
Even
Odd
Even
Odd
Even
Odd
Even
THREE
SUBPROGRAMS
3.1 Subprograms
So far, we have used procedures, mostly to have a main body of code to execute. Proce-
dures are one kind of subprogram.
There are two kinds of subprograms in Ada, functions and procedures. The distinction
between the two is that a function returns a value, and a procedure does not.
This example shows the declaration and definition of a function:
Listing 1: increment.ads
1 function Increment (I : Integer) return Integer;
Listing 2: increment.adb
1 -- We declare (but don't define) a function with
2 -- one parameter, returning an integer value
3
Project: Courses.Intro_To_Ada.Subprograms.Increment
MD5: 582fe283730a130cec071c455a0ce3d4
Subprograms in Ada can, of course, have parameters. One syntactically important note is
that a subprogram which has no parameters does not have a parameter section at all, for
example:
procedure Proc;
Listing 3: increment_by.ads
1 function Increment_By
2 (I : Integer := 0;
3 Incr : Integer := 1) return Integer;
4 -- ^ Default value for parameters
19
Introduction to Ada
Project: Courses.Intro_To_Ada.Subprograms.Increment_By
MD5: 5728b915789beee0b5546ea7b36a1cc2
In this example, we see that parameters can have default values. When calling the subpro-
gram, you can then omit parameters if they have a default value. Unlike C/C++, a call to a
subprogram without parameters does not include parentheses.
This is the implementation of the function above:
Listing 4: increment_by.adb
1 function Increment_By
2 (I : Integer := 0;
3 Incr : Integer := 1) return Integer is
4 begin
5 return I + Incr;
6 end Increment_By;
Project: Courses.Intro_To_Ada.Subprograms.Increment_By
MD5: 07c85e5c1272ea396bf4dbc0cefcdce7
The Ada standard doesn't mandate in which file the specification or the implementation
of a subprogram must be stored. In other words, the standard doesn't require a specific
file structure or specific file name extensions. For example, we could save both the
specification and the implementation of the Increment function above in a file called
increment.txt. (We could even store the entire source code of a system in a single
file.) From the standard's perspective, this would be completely acceptable.
The GNAT toolchain, however, requires the following file naming scheme:
• files with the .ads extension contain the specification, while
• files with the .adb extension contain the implementation.
Therefore, in the GNAT toolchain, the specification of the Increment function must
be stored in the increment.ads file, while its implementation must be stored in the
increment.adb file. This rule always applies to packages, which we discuss later
(page 31). (Note, however, that it's possible to circumvent this rule.) For more de-
tails, you may refer to the Introduction to GNAT Toolchain course or the GPRbuild User’s
Guide9 .
Listing 5: show_increment.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Increment_By;
3
4 procedure Show_Increment is
5 A, B, C : Integer;
6 begin
7 C := Increment_By;
(continues on next page)
9 https://docs.adacore.com/gprbuild-docs/html/gprbuild_ug.html
20 Chapter 3. Subprograms
Introduction to Ada
15 A := 10;
16 B := 3;
17 C := Increment_By (A, B);
18 -- ^ Regular parameter passing
19
27 A := 20;
28 B := 5;
29 C := Increment_By (I => A,
30 Incr => B);
31 -- ^ Named parameter passing
32
Project: Courses.Intro_To_Ada.Subprograms.Increment_By
MD5: dcb501c8c6815b03c6841fc8b80d6911
Runtime output
Ada allows you to name the parameters when you pass them, whether they have a default
or not. There are some rules:
• Positional parameters come first.
• A positional parameter cannot follow a named parameter.
As a convention, people usually name parameters at the call site if the function's corre-
sponding parameters has a default value. However, it is also perfectly acceptable to name
every parameter if it makes the code clearer.
3.1. Subprograms 21
Introduction to Ada
• It lets you organize your programs in a cleaner fashion. If you need a subprogram only
as a "helper" for another subprogram, then the principle of localization indicates that
the helper subprogram should be declared nested.
• It allows you to share state easily in a controlled fashion, because the nested subpro-
grams have access to the parameters, as well as any local variables, declared in the
outer scope.
For the previous example, we can move the duplicated code (call to Put_Line) to a separate
procedure. This is a shortened version with the nested Display_Result procedure.
Listing 6: show_increment.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Increment_By;
3
4 procedure Show_Increment is
5 A, B, C : Integer;
6
7 procedure Display_Result is
8 begin
9 Put_Line ("Increment of "
10 & Integer'Image (A)
11 & " with "
12 & Integer'Image (B)
13 & " is "
14 & Integer'Image (C));
15 end Display_Result;
16
17 begin
18 A := 10;
19 B := 3;
20 C := Increment_By (A, B);
21 Display_Result;
22 A := 20;
23 B := 5;
24 C := Increment_By (A, B);
25 Display_Result;
26 end Show_Increment;
Project: Courses.Intro_To_Ada.Subprograms.Increment_By
MD5: 23ec8ae3080c042123a9e82ee6b3d9e3
Runtime output
Increment of 10 with 3 is 13
Increment of 20 with 5 is 25
Listing 7: quadruple.ads
1 function Quadruple (I : Integer)
2 return Integer;
22 Chapter 3. Subprograms
Introduction to Ada
Listing 8: quadruple.adb
1 function Quadruple (I : Integer)
2 return Integer is
3
10 Res : Integer;
11 begin
12 Double (Double (I));
13 -- ERROR: cannot use call to function
14 -- "Double" as a statement
15
20 return Res;
21 end Quadruple;
Project: Courses.Intro_To_Ada.Subprograms.Quadruple
MD5: fd525bde1a883d4a3794730695af6469
Build output
A statement such as Double (Double (I)); is wrong because we're not assigning the
return value to a variable — we can correct this statement by writing Res := Double
(Double (I));.
In GNAT, with all warnings activated, it becomes even harder to ignore the result of a
function, because unused variables will be flagged. For example, this code would not be
valid:
function Read_Int
(Stream : Network_Stream;
Result : out Integer) return Boolean;
procedure Main is
Stream : Network_Stream := Get_Stream;
My_Int : Integer;
3.1. Subprograms 23
Introduction to Ada
• Or give the variable a name that contains any of the strings discard dummy ignore
junk unused (case insensitive)
The default mode for parameters is in; so far, most of the examples have been using in
parameters.
® Historically
Functions and procedures were originally more different in philosophy. Before Ada 2012,
functions could only take in parameters.
Listing 9: swap.adb
1 procedure Swap (A, B : Integer) is
2 Tmp : Integer;
3 begin
4 Tmp := A;
5
24 Chapter 3. Subprograms
Introduction to Ada
Project: Courses.Intro_To_Ada.Subprograms.Swap
MD5: 478ac23f878934aae820e4b9c056d939
Build output
The fact that in is the default mode is very important. It means that a parameter will not
be modified unless you explicitly specify a mode in which modification is allowed.
3 procedure In_Out_Params is
4 procedure Swap (A, B : in out Integer) is
5 Tmp : Integer;
6 begin
7 Tmp := A;
8 A := B;
9 B := Tmp;
10 end Swap;
11
12 A : Integer := 12;
13 B : Integer := 44;
14 begin
15 Swap (A, B);
16
17 -- Prints 44
18 Put_Line (Integer'Image (A));
19 end In_Out_Params;
Project: Courses.Intro_To_Ada.Subprograms.In_Out_Params
MD5: 319358e479449c115cf2b3cbb4ff3a6b
Runtime output
44
An in out parameter will allow read and write access to the object passed as parameter,
so in the example above, we can see that A is modified after the call to Swap.
Á Attention
While in out parameters look a bit like references in C++, or regular parameters in
Java that are passed by-reference, the Ada language standard does not mandate "by
reference" passing for in out parameters except for certain categories of types as will
be explained later.
In general, it is better to think of modes as higher level than by-value versus by-reference
semantics. For the compiler, it means that an array passed as an in parameter might
be passed by reference, because it is more efficient (which does not change anything
for the user since the parameter is not assignable). However, a parameter of a discrete
type will always be passed by copy, regardless of its mode (which is more efficient on
most architectures).
® In other languages
Ada doesn't have a tuple construct and does not allow returning multiple values from
a subprogram (except by declaring a full-fledged record type). Hence, a way to return
multiple values from a subprogram is to use out parameters.
For example, a procedure reading integers from the network could have one of the following
specifications:
procedure Read_Int
(Stream : Network_Stream;
Success : out Boolean;
Result : out Integer);
function Read_Int
(Stream : Network_Stream;
Result : out Integer) return Boolean;
While reading an out variable before writing to it should, ideally, trigger an error, imposing
that as a rule would cause either inefficient run-time checks or complex compile-time rules.
So from the user's perspective an out parameter acts like an uninitialized variable when the
subprogram is invoked.
GNAT will detect simple cases of incorrect use of out parameters. For example, the
compiler will emit a warning for the following program:
26 Chapter 3. Subprograms
Introduction to Ada
7 A := B;
8 end Foo;
9 begin
10 null;
11 end Outp;
Code block metadata
Project: Courses.Intro_To_Ada.Subprograms.Out_Params
MD5: 36bdb4e541297d7fb0b075816cb6e73a
Build output
outp.adb:3:22: warning: "A" may be referenced before it has a value [enabled by␣
↪default]
Project: Courses.Intro_To_Ada.Subprograms.Mutually_Recursive_Subprograms
MD5: 5ee030cdecc6c4aea8916cbb763e8526
3.4 Renaming
Subprograms can be renamed by using the renames keyword and declaring a new name
for a subprogram:
3.4. Renaming 27
Introduction to Ada
This can be useful, for example, to improve the readability of your application when you're
using code from external sources that cannot be changed in your system. Let's look at an
example:
3 procedure A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed
4 (A_Message : String) is
5 begin
6 Put_Line (A_Message);
7 end A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;
As the wording in the name of procedure above implies, we cannot change its name. We
can, however, rename it to something like Show in our test application and use this shorter
name. Note that we also have to declare all parameters of the original subprogram — we
may rename them, too, in the declaration. For example:
3 procedure Show_Renaming is
4
8 begin
9 Show ("Hello World!");
10 end Show_Renaming;
Runtime output
Hello World!
3 procedure Show_Image_Renaming is
(continues on next page)
28 Chapter 3. Subprograms
Introduction to Ada
8 begin
9 Put_Line (Img (2));
10 Put_Line (Img (3));
11 end Show_Image_Renaming;
Project: Courses.Intro_To_Ada.Subprograms.Integer_Image_Renaming
MD5: 9843b9d5967679c4fe8bd83a5213829f
Runtime output
2
3
Renaming also allows us to introduce default expressions that were not available in the
original declaration. For example, we may specify "Hello World!" as the default for the
String parameter of the Show procedure:
with A_Procedure_With_Very_Long_Name_That_Cannot_Be_Changed;
procedure Show_Renaming_Defaults is
begin
Show;
end Show_Renaming_Defaults;
3.4. Renaming 29
Introduction to Ada
30 Chapter 3. Subprograms
CHAPTER
FOUR
MODULAR PROGRAMMING
So far, our examples have been simple standalone subprograms. Ada is helpful in that
regard, since it allows arbitrary declarations in a declarative part. We were thus able to
declare our types and variables in the bodies of main procedures.
However, it is easy to see that this is not going to scale up for real-world applications. We
need a better way to structure our programs into modular and distinct units.
Ada encourages the separation of programs into multiple packages and sub-packages, pro-
viding many tools to a programmer on a quest for a perfectly organized code-base.
4.1 Packages
Here is an example of a package declaration in Ada:
Listing 1: week.ads
1 package Week is
2
11 end Week;
Project: Courses.Intro_To_Ada.Modular_Programming.Week
MD5: 0fa033dc8fe2b9741483de273354e7ee
Listing 2: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Week;
3 -- References the Week package, and
4 -- adds a dependency from Main to Week
5
6 procedure Main is
7 begin
8 Put_Line ("First day of the week is "
9 & Week.Mon);
10 end Main;
31
Introduction to Ada
Project: Courses.Intro_To_Ada.Modular_Programming.Week
MD5: 03e17a75620de6a397b1d3c5a3e22f6a
Runtime output
Packages let you make your code modular, separating your programs into semantically
significant units. Additionally the separation of a package's specification from its body
(which we will see below) can reduce compilation time.
While the with clause indicates a dependency, you can see in the example above that you
still need to prefix the referencing of entities from the Week package by the name of the
package. (If we had included a use Week clause, then such a prefix would not have been
necessary.)
Accessing entities from a package uses the dot notation, A.B, which is the same notation
as the one used to access record fields.
A with clause can only appear in the prelude of a compilation unit (i.e., before the reserved
word, such as procedure, that marks the beginning of the unit). It is not allowed anywhere
else. This rule is only needed for methodological reasons: the person reading your code
should be able to see immediately which units the code depends on.
® In other languages
Packages look similar to, but are semantically very different from, header files in C/C++.
• The first and most important distinction is that packages are a language-level mech-
anism. This is in contrast to a #include'd header file, which is a functionality of the
C preprocessor.
• An immediate consequence is that the with construct is a semantic inclusion mech-
anism, not a text inclusion mechanism. Hence, when you with a package, you are
saying to the compiler "I'm depending on this semantic unit", and not "include this
bunch of text in place here".
• The effect of a package thus does not vary depending on where it has been withed
from. Contrast this with C/C++, where the meaning of the included text depends
on the context in which the #include appears.
This allows compilation/recompilation to be more efficient. It also allows tools like
IDEs to have correct information about the semantics of a program. In turn, this
allows better tooling in general, and code that is more analyzable, even by humans.
An important benefit of Ada with clauses when compared to #include is that it is state-
less. The order of with and use clauses does not matter, and can be changed without
side effects.
The Ada language standard does not mandate any particular relationship between
source files and packages; for example, in theory you can put all your code in one
file, or use your own file naming conventions. In practice, however, an implementa-
tion will have specific rules. With GNAT, each top-level compilation unit needs to go into
a separate file. In the example above, the Week package will be in an .ads file (for Ada
specification), and the Main procedure will be in an .adb file (for Ada body).
Listing 3: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 -- ^ Make every entity of the
3 -- Ada.Text_IO package
4 -- directly visible.
5 with Week;
6
7 procedure Main is
8 use Week;
9 -- Make every entity of the Week
10 -- package directly visible.
11 begin
12 Put_Line ("First day of the week is " & Mon);
13 end Main;
Project: Courses.Intro_To_Ada.Modular_Programming.Week
MD5: ea54077d4ae165b28ae8facfe8ba2db7
Runtime output
Listing 4: operations.ads
1 package Operations is
2
3 -- Declaration
4 function Increment_By
5 (I : Integer;
6 Incr : Integer := 0) return Integer;
7
10 end Operations;
Listing 5: operations.adb
1 package body Operations is
2
3 Last_Increment : Integer := 1;
4
5 function Increment_By
6 (I : Integer;
7 Incr : Integer := 0) return Integer is
8 begin
9 if Incr /= 0 then
10 Last_Increment := Incr;
11 end if;
12
13 return I + Last_Increment;
14 end Increment_By;
15
21 end Operations;
Project: Courses.Intro_To_Ada.Modular_Programming.Operations
MD5: 2adfb64e825605c74fecf6c9d45c8437
Here we can see that the body of the Increment_By function has to be declared in the
body. Coincidentally, introducing a body allows us to put the Last_Increment variable in
the body, and make them inaccessible to the user of the Operations package, providing a
first form of encapsulation.
This works because entities declared in the body are only visible in the body.
This example shows how Last_Increment is used indirectly:
Listing 6: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Operations;
3
4 procedure Main is
5 use Operations;
6
7 I : Integer := 0;
8 R : Integer;
9
10 procedure Display_Update_Values is
11 Incr : constant Integer :=
12 Get_Increment_Value;
13 begin
14 Put_Line (Integer'Image (I)
15 & " incremented by "
16 & Integer'Image (Incr)
17 & " is "
18 & Integer'Image (R));
19 I := R;
20 end Display_Update_Values;
21 begin
22 R := Increment_By (I);
(continues on next page)
Project: Courses.Intro_To_Ada.Modular_Programming.Operations
MD5: 76190b1261a9652cfb7986ecec191e37
Runtime output
0 incremented by 1 is 1
1 incremented by 1 is 2
2 incremented by 5 is 7
7 incremented by 5 is 12
12 incremented by 10 is 22
22 incremented by 10 is 32
® Important
Ada also supports nested packages. However, since they can be more complicated to
use, the recommendation is to use child packages instead. Nested packages will be
covered in the advanced course.
Let's begin our discussion on child packages by taking our previous Week package:
Listing 7: week.ads
1 package Week is
2
11 end Week;
Project: Courses.Intro_To_Ada.Modular_Programming.Child_Packages
MD5: 0fa033dc8fe2b9741483de273354e7ee
Listing 8: week-child.ads
1 package Week.Child is
2
5 end Week.Child;
Project: Courses.Intro_To_Ada.Modular_Programming.Child_Packages
MD5: a7db38e772cf6153b5eb95069517e833
Here, Week is the parent package and Child is the child package. This is the corresponding
package body of Week.Child:
Listing 9: week-child.adb
1 package body Week.Child is
2
8 end Week.Child;
Project: Courses.Intro_To_Ada.Modular_Programming.Child_Packages
MD5: 04dad82685ad9f0231c3084266b0af83
In the implementation of the Get_First_Of_Week function, we can use the Mon string di-
rectly, even though it was declared in the parent package Week. We don't write with Week
here because all elements from the specification of the Week package — such as Mon, Tue
and so on — are visible in the child package Week.Child.
Now that we've completed the implementation of the Week.Child package, we can use
elements from this child package in a subprogram by simply writing with Week.Child.
Similarly, if we want to use these elements directly, we write use Week.Child in addition.
For example:
4 procedure Main is
5 begin
6 Put_Line ("First day of the week is "
(continues on next page)
Project: Courses.Intro_To_Ada.Modular_Programming.Child_Packages
MD5: e2f5c6ad3a92da4cb04ee7ec12293df4
Runtime output
5 end Week.Child.Grandchild;
8 end Week.Child.Grandchild;
Project: Courses.Intro_To_Ada.Modular_Programming.Child_Packages
MD5: 03ee5932a68212b2e501370212508ab1
We can use this new Grandchild package in our test application in the same way as before:
we can reuse the previous test application and adapt the with and use, and the function
call. This is the updated code:
3 with Week.Child.Grandchild;
4 use Week.Child.Grandchild;
5
6 procedure Main is
7 begin
8 Put_Line ("Second day of the week is "
9 & Get_Second_Of_Week);
10 end Main;
Runtime output
Second day of the week is Tuesday
Again, this isn't the limit for the package hierarchy. We could continue to extend
the hierarchy of the previous example by implementing a Week.Child.Grandchild.
Grand_grandchild package.
5 end Week.Child_2;
Here, Week is still the parent package of the Child package, but it's also the parent of the
Child_2 package. In the same way, Child_2 is obviously one of the child packages of Week.
This is the corresponding package body of Week.Child_2:
8 end Week.Child_2;
5 procedure Main is
(continues on next page)
Runtime output
First day of the week is Monday
Last day of the week is Sunday
4.4.3 Visibility
In the previous section, we've seen that elements declared in a parent package specification
are visible in the child package. This is, however, not the case for elements declared in the
package body of a parent package.
Let's consider the package Book and its child Additional_Operations:
10 end Book;
7 end Book.Additional_Operations;
16 end Book;
19 end Book.Additional_Operations;
Project: Courses.Intro_To_Ada.Modular_Programming.Visibility
MD5: 68b7490da12bafae0aa6fe0ab76c6b1c
In the implementation of the Get_Extended_Title, we're using the Title constant from the
parent package Book. However, as indicated in the comments of the Get_Extended_Author
function, the Author string — which we declared in the body of the Book package — isn't
visible in the Book.Additional_Operations package. Therefore, we cannot use it to im-
plement the Get_Extended_Author function.
We can, however, use the Get_Author function from Book in the implementation of the
Get_Extended_Author function to retrieve this string. Likewise, we can use this strategy
to implement the Get_Extended_Title function. This is the adapted code:
13 end Book.Additional_Operations;
Project: Courses.Intro_To_Ada.Modular_Programming.Visibility
MD5: b00c187cb54d3fcb9574726028c1efc6
3 with Book.Additional_Operations;
4 use Book.Additional_Operations;
5
6 procedure Main is
7 begin
8 Put_Line (Get_Extended_Title);
9 Put_Line (Get_Extended_Author);
10 end Main;
Project: Courses.Intro_To_Ada.Modular_Programming.Visibility
MD5: bdc75987fe61e9401b400f8704890ebe
Runtime output
4.5 Renaming
Previously, we've mentioned that subprograms can be renamed (page 27). We can re-
name packages, too. Again, we use the renames keyword for that. The following example
renames the Ada.Text_IO package as TIO:
3 procedure Main is
4 package TIO renames Ada.Text_IO;
5 begin
6 TIO.Put_Line ("Hello");
7 end Main;
4.5. Renaming 41
Introduction to Ada
Project: Courses.Intro_To_Ada.Modular_Programming.Rename_Text_IO
MD5: 33652dd004ef33d95c168ab8893cd412
Runtime output
Hello
We can use renaming to improve the readability of our code by using shorter package
names. In the example above, we write TIO.Put_Line instead of the longer version (Ada.
Text_IO.Put_Line). This approach is especially useful when we don't use packages and
want to avoid that the code becomes too verbose.
Note we can also rename subprograms and objects inside packages. For instance, we could
have just renamed the Put_Line procedure in the source code example above:
3 procedure Main is
4 procedure Say (Something : String)
5 renames Ada.Text_IO.Put_Line;
6 begin
7 Say ("Hello");
8 end Main;
Project: Courses.Intro_To_Ada.Modular_Programming.Rename_Put_Line
MD5: f30174ff29eb01f33bc95f1787f9f1dc
Runtime output
Hello
FIVE
Ada is a strongly typed language. It is interestingly modern in that respect: strong static
typing has been increasing in popularity in programming language design, owing to factors
such as the growth of statically typed functional programming, a big push from the research
community in the typing domain, and many practical languages with strong type systems.
5.2 Integers
A nice feature of Ada is that you can define your own integer types, based on the require-
ments of your program (i.e., the range of values that makes sense). In fact, the definitional
mechanism that Ada provides forms the semantic basis for the predefined integer types.
There is no "magical" built-in type in that regard, which is unlike most languages, and ar-
guably very elegant.
Listing 1: integer_type_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Integer_Type_Example is
4 -- Declare a signed integer type,
5 -- and give the bounds
6 type My_Int is range -1 .. 20;
7 -- ^ High bound
8 -- ^ Low bound
9
43
Introduction to Ada
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Integer_Type_Example
MD5: 1d82fa54b604944fdd8652cbf84f4ff2
Runtime output
-1
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
This example illustrates the declaration of a signed integer type, and several things we can
do with them.
Every type declaration in Ada starts with the type keyword (except for task types
(page 156)). After the type, we can see a range that looks a lot like the ranges that we
use in for loops, that defines the low and high bound of the type. Every integer in the
inclusive range of the bounds is a valid value for the type.
In Ada, an integer type is not specified in terms of its machine representation, but rather
by its range. The compiler will then choose the most appropriate representation.
Another point to note in the above example is the My_Int'Image (I) expression. The
Name'Attribute (optional params) notation is used for what is called an attribute in
Ada. An attribute is a built-in operation on a type, a value, or some other program entity.
It is accessed by using a ' symbol (the ASCII apostrophe).
Ada has several types available as "built-ins"; Integer is one of them. Here is how Integer
might be defined for a typical processor:
type Integer is
range -(2 ** 31) .. +(2 ** 31 - 1);
** is the exponent operator, which means that the first valid value for Integer is -231 , and
the last valid value is 231 - 1.
Ada does not mandate the range of the built-in type Integer. An implementation for a
16-bit target would likely choose the range -215 through 215 - 1.
Listing 2: main.adb
1 procedure Main is
2 A : Integer := Integer'Last;
3 B : Integer;
4 begin
5 B := A + 5;
6 -- This operation will overflow, eg. it
7 -- will raise an exception at run time.
8 end Main;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Overflow_Check
MD5: bddd15b394f043442024899d12b982fb
Build output
Runtime output
Listing 3: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Main is
4 type My_Int is range 1 .. 20;
5 A : My_Int := 12;
6 B : My_Int := 15;
7 M : My_Int := (A + B) / 2;
8 -- No overflow here, overflow checks
9 -- are done at specific boundaries.
10 begin
11 for I in 1 .. M loop
12 Put_Line ("Hello, World!");
(continues on next page)
5.2. Integers 45
Introduction to Ada
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Overflow_Check_2
MD5: d24283cbb42c0be5b5fa215eb16ad2e7
Runtime output
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Type-level overflow will only be checked at specific points in the execution. The result,
as we see above, is that you might have an operation that overflows in an intermediate
computation, but no exception will be raised because the final result does not overflow.
Listing 4: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Main is
4 type Mod_Int is mod 2 ** 5;
5 -- ^ Range is 0 .. 31
6
10 M : constant Mod_Int := A + B;
11 -- No overflow here,
12 -- M = (20 + 15) mod 32 = 3
13 begin
14 for I in 1 .. M loop
15 Put_Line ("Hello, World!");
16 end loop;
17 end Main;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Unsigned_Types
MD5: df4efee4eb29e7ea15a0cf961b600dd5
Runtime output
Hello, World!
Hello, World!
Hello, World!
Unlike in C/C++, since this wraparound behavior is guaranteed by the Ada specification,
you can rely on it to implement portable code. Also, being able to leverage the wrapping
on arbitrary bounds is very useful — the modulus does not need to be a power of 2 — to
implement certain algorithms and data structures, such as ring buffers10 .
5.4 Enumerations
Enumeration types are another nicety of Ada's type system. Unlike C's enums, they are not
integers, and each new enumeration type is incompatible with other enumeration types.
Enumeration types are part of the bigger family of discrete types, which makes them usable
in certain situations that we will describe later but one context that we have already seen
is a case statement.
Listing 5: enumeration_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Enumeration_Example is
4 type Days is (Monday, Tuesday, Wednesday,
5 Thursday, Friday,
6 Saturday, Sunday);
7 -- An enumeration type
8 begin
9 for I in Days loop
10 case I is
11 when Saturday .. Sunday =>
12 Put_Line ("Week end!");
13
Runtime output
Hello on MONDAY
Hello on TUESDAY
Hello on WEDNESDAY
Hello on THURSDAY
Hello on FRIDAY
(continues on next page)
10 https://en.wikipedia.org/wiki/Circular_buffer
5.4. Enumerations 47
Introduction to Ada
Enumeration types are powerful enough that, unlike in most languages, they're used to
define the standard Boolean type:
As mentioned previously, every "built-in" type in Ada is defined with facilities generally
available to the user.
Listing 6: floating_point_demo.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Floating_Point_Demo is
4 A : constant Float := 2.5;
5 begin
6 Put_Line ("The value of A is "
7 & Float'Image (A));
8 end Floating_Point_Demo;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Floating_Point_Demo
MD5: 06998775497b68b742700138faecbb6a
Runtime output
Listing 7: floating_point_operations.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Floating_Point_Operations is
4 A : Float := 2.5;
5 begin
6 A := abs (A - 4.5);
7 Put_Line ("The value of A is "
8 & Float'Image (A));
9
10 A := A ** 2 + 1.0;
11 Put_Line ("The value of A is "
(continues on next page)
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Floating_Point_Operations
MD5: c280e0f23e020aaee1a8777e7fb4c242
Runtime output
The value of A is 2.0 after the first operation and 5.0 after the second operation.
In addition to Float, an Ada implementation may offer data types with higher precision such
as Long_Float and Long_Long_Float. Like Float, the standard does not indicate the exact
precision of these types: it only guarantees that the type Long_Float, for example, has at
least the precision of Float. In order to guarantee that a certain precision requirement is
met, we can define custom floating-point types, as we will see in the next section.
The compiler will choose a floating-point representation that supports the required preci-
sion. For example:
Listing 8: custom_floating_types.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Custom_Floating_Types is
4 type T3 is digits 3;
5 type T15 is digits 15;
6 type T18 is digits 18;
7 begin
8 Put_Line ("T3 requires "
9 & Integer'Image (T3'Size)
10 & " bits");
11 Put_Line ("T15 requires "
12 & Integer'Image (T15'Size)
13 & " bits");
14 Put_Line ("T18 requires "
15 & Integer'Image (T18'Size)
16 & " bits");
17 end Custom_Floating_Types;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Custom_Floating_Types
MD5: 3c23738f13e081038996c533da8fb723
Runtime output
T3 requires 32 bits
T15 requires 64 bits
T18 requires 128 bits
In this example, the attribute 'Size is used to retrieve the number of bits used for the
specified data type. As we can see by running this example, the compiler allocates 32
bits for T3, 64 bits for T15 and 128 bits for T18. This includes both the mantissa and the
exponent.
The number of digits specified in the data type is also used in the format when displaying
floating-point variables. For example:
Listing 9: display_custom_floating_types.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Display_Custom_Floating_Types is
4 type T3 is digits 3;
5 type T18 is digits 18;
6
7 C1 : constant := 1.0e-4;
8
Runtime output
The value of A is 1.00E+00
The value of B is 1.00010000000000000E+00
As expected, the application will display the variables according to specified precision
(1.00E+00 and 1.00010000000000000E+00).
3 procedure Floating_Point_Range is
4 type T_Norm is new Float range -1.0 .. 1.0;
5 A : T_Norm;
6 begin
7 A := 1.0;
8 Put_Line ("The value of A is "
(continues on next page)
Runtime output
The value of A is 1.00000E+00
The application is responsible for ensuring that variables of this type stay within this range;
otherwise an exception is raised. In this example, the exception Constraint_Error is
raised when assigning 2.0 to the variable A:
3 procedure Floating_Point_Range_Exception is
4 type T_Norm is new Float range -1.0 .. 1.0;
5 A : T_Norm;
6 begin
7 A := 2.0;
8 Put_Line ("The value of A is "
9 & T_Norm'Image (A));
10 end Floating_Point_Range_Exception;
MD5: ecda66589ba28e453956dca159ea5f0d
Build output
floating_point_range_exception.adb:7:09: warning: value not in range of type "T_
↪Norm" defined at line 4 [enabled by default]
Runtime output
Ranges can also be specified for custom floating-point types. For example:
4 procedure Custom_Range_Types is
5 type T6_Inv_Trig is
6 digits 6 range -Pi / 2.0 .. Pi / 2.0;
7 begin
8 null;
9 end Custom_Range_Types;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Custom_Range_Types
MD5: 7b62abc869290a30e351163f670059e0
In this example, we are defining a type called T6_Inv_Trig, which has a range from -π /
2 to π / 2 with a minimum precision of 6 digits. (Pi is defined in the predefined package
Ada.Numerics.)
3 procedure Illegal_Example is
4 -- Declare two different floating point types
5 type Meters is new Float;
6 type Miles is new Float;
7
8 Dist_Imperial : Miles;
9
10 -- Declare a constant
11 Dist_Metric : constant Meters := 1000.0;
12 begin
13 -- Not correct: types mismatch
14 Dist_Imperial := Dist_Metric * 621.371e-6;
15 Put_Line (Miles'Image (Dist_Imperial));
16 end Illegal_Example;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Imperial_Metric_Error
MD5: e28e341c5eda9b3b4cef691fa24b7f7e
Build output
A consequence of these rules is that, in the general case, a "mixed mode" expression like
2 * 3.0 will trigger a compilation error. In a language like C or Python, such expressions
are made valid by implicit conversions. In Ada, such conversions must be made explicit:
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Imperial_Metric
MD5: e455641e86227e80e5f920b5af6315d4
Runtime output
6.21371E-01
Of course, we probably do not want to write the conversion code every time we convert
from meters to miles. The idiomatic Ada way in that case would be to introduce conversion
functions along with the types.
3 procedure Conv is
4 type Meters is new Float;
5 type Miles is new Float;
6
15 Dist_Imperial : Miles;
16 Dist_Metric : constant Meters := 1000.0;
17 begin
18 Dist_Imperial := To_Miles (Dist_Metric);
19 Put_Line (Miles'Image (Dist_Imperial));
20 end Conv;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Imperial_Metric_Func
MD5: 661737fa9f130ac3070210bbf6f08214
Runtime output
6.21371E-01
If you write a lot of numeric code, having to explicitly provide such conversions might seem
painful at first. However, this approach brings some advantages. Notably, you can rely on
the absence of implicit conversions, which will in turn prevent some subtle errors.
® In other languages
In C, for example, the rules for implicit conversions may not always be com-
pletely obvious. In Ada, however, the code will always do exactly what it seems
to do. For example:
int a = 3, b = 2;
float f = a / b;
This code will compile fine, but the result of f will be 1.0 instead of 1.5, be-
cause the compiler will generate an integer division (three divided by two)
that results in one. The software developer must be aware of data conversion
issues and use an appropriate casting:
int a = 3, b = 2;
float f = (float)a / b;
In the corrected example, the compiler will convert both variables to their cor-
responding floating-point representation before performing the division. This
will produce the expected result.
This example is very simple, and experienced C developers will probably notice
and correct it before it creates bigger problems. However, in more complex
applications where the type declaration is not always visible — e.g. when
referring to elements of a struct — this situation might not always be evident
and quickly lead to software defects that can be harder to find.
The Ada compiler, in contrast, will always reject code that mixes floating-point
and integer variables without explicit conversion. The following Ada code,
based on the erroneous example in C, will not compile:
Build output
main.adb:6:11: error: expected type "Standard.Float"
main.adb:6:11: error: found type "Standard.Integer"
gprbuild: *** compilation phase failed
11 SSN : Social_Security_Number :=
12 555_55_5555;
13 -- ^ You can put underscores as
14 -- formatting in any number.
15
16 I : Integer;
17
26 -- Likewise illegal:
27 SSN := I;
28
32 -- Likewise OK:
33 SSN := Social_Security_Number (I);
34 end Main;
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Derived_Types
MD5: 63445601ddb5e52dceab095d3305623a
Build output
The type Social_Security is said to be a derived type; its parent type is Integer.
As illustrated in this example, you can refine the valid range when defining a derived scalar
type (such as integer, floating-point and enumeration).
The syntax for enumerations uses the range <range> syntax:
3 procedure Greet is
4 type Days is (Monday, Tuesday, Wednesday,
5 Thursday, Friday,
6 Saturday, Sunday);
7
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Days
MD5: 853b5c1576961c7c20d4306275122364
5.8 Subtypes
As we are starting to see, types may be used in Ada to enforce constraints on the valid
range of values. However, we sometimes want to enforce constraints on some values while
staying within a single type. This is where subtypes come into play. A subtype does not
introduce a new type.
3 procedure Greet is
4 type Days is (Monday, Tuesday, Wednesday,
5 Thursday, Friday,
6 Saturday, Sunday);
7
8 -- Declaration of a subtype
9 subtype Weekend_Days is
10 Days range Saturday .. Sunday;
11 -- ^ Constraint of the subtype
12
13 M : Days := Sunday;
14
15 S : Weekend_Days := M;
16 -- No error here, Days and Weekend_Days
17 -- are of the same type.
18 begin
19 for I in Days loop
20 case I is
21 -- Just like a type, a subtype can
22 -- be used as a range
23 when Weekend_Days =>
24 Put_Line ("Week end!");
25 when others =>
26 Put_Line ("Hello on "
27 & Days'Image (I));
28 end case;
(continues on next page)
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Days_Subtype
MD5: 8ee7127d152a8b2c9d0ac74d05fc2fc2
Runtime output
Hello on MONDAY
Hello on TUESDAY
Hello on WEDNESDAY
Hello on THURSDAY
Hello on FRIDAY
Week end!
Week end!
Several subtypes are predefined in the standard package in Ada, and are automatically
available to you:
While subtypes of a type are statically compatible with each other, constraints are enforced
at run time: if you violate a subtype constraint, an exception will be raised.
3 procedure Greet is
4 type Days is (Monday, Tuesday, Wednesday,
5 Thursday, Friday,
6 Saturday, Sunday);
7
8 subtype Weekend_Days is
9 Days range Saturday .. Sunday;
10
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Days_Subtype_Error
MD5: 84d42d276d26544f35edab5870459378
Build output
5.8. Subtypes 57
Introduction to Ada
Runtime output
3 procedure Undetected_Imperial_Metric_Error is
4 -- Declare two type aliases
5 subtype Meters is Float;
6 subtype Miles is Float;
7
8 Dist_Imperial : Miles;
9
10 -- Declare a constant
11 Dist_Metric : constant Meters := 100.0;
12 begin
13 -- No conversion to Miles type required:
14 Dist_Imperial := (Dist_Metric * 1609.0)
15 / 1000.0;
16
Project: Courses.Intro_To_Ada.Strongly_Typed_Language.Undetected_Imperial_Metric_
↪Error
MD5: cdb8f949c69f3c480502b859dac298ee
Runtime output
1.00000E+02
In the example above, the fact that both Meters and Miles are subtypes of Float allows
us to mix variables of both types without type conversion. This, however, can lead to all
sorts of programming mistakes that we'd like to avoid, as we can see in the undetected
error highlighted in the code above. In that example, the error in the assignment of a value
in meters to a variable meant to store values in miles remains undetected because both
Meters and Miles are subtypes of Float. Therefore, the recommendation is to use strong
typing — via type X is new Y — for cases such as the one above.
There are, however, many situations where type aliases are useful. For example, in an
application that uses floating-point types in multiple contexts, we could use type aliases
to indicate additional meaning to the types or to avoid long variable names. For example,
instead of writing:
We could write:
® In other languages
In C, for example, we can use a typedef declaration to create a type alias. For example:
typedef float meters;
This corresponds to the declaration that we've seen above using subtypes. Other pro-
gramming languages include this concept in similar ways. For example:
• C++: using meters = float;
• Swift: typealias Meters = Double
• Kotlin: typealias Meters = Double
• Haskell: type Meters = Float
Note, however, that subtypes in Ada correspond to type aliases if, and only if, they don't
have new constraints. Thus, if we add a new constraint to a subtype declaration, we don't
have a type alias anymore. For example, the following declaration can't be considered a
type alias of Float:
subtype Liquid_Water_Temperature is
Degree_Celsius range 0.0 .. 100.0;
subtype Running_Water_Temperature is
Liquid_Water_Temperature;
5.8. Subtypes 59
Introduction to Ada
SIX
RECORDS
So far, all the types we have encountered have values that are not decomposable: each
instance represents a single piece of data. Now we are going to see our first class of com-
posite types: records.
Records allow composing a value out of instances of other types. Each of those instances
will be given a name. The pair consisting of a name and an instance of a specific type is
called a field, or a component.
Fields look a lot like variable declarations, except that they are inside of a record definition.
And as with variable declarations, you can specify additional constraints when supplying
the subtype of the field.
Record components can have default values. When a variable having the record type is
declared, a field with a default initialization will be automatically set to this value. The
value can be any expression of the component type, and may be run-time computable.
In the remaining sections of this chapter, we see how to use record types. In addition to
that, we discuss more about records in another chapter (page 97).
61
Introduction to Ada
6.2 Aggregates
-- Positional components
Ada_Birthday : Date := (10, December, 1815);
-- Named components
Leap_Day_2020 : Date := (Day => 29,
Month => February,
Year => 2020);
-- ^ By name
Records have a convenient notation for expressing values, illustrated above. This notation
is called aggregate notation, and the literals are called aggregates. They can be used in
a variety of contexts that we will see throughout the course, one of which is to initialize
records.
An aggregate is a list of values separated by commas and enclosed in parentheses. It is
allowed in any context where a value of the record is expected.
Values for the components can be specified positionally, as in Ada_Birthday example, or
by name, as in Leap_Day_2020. A mixture of positional and named values is permitted, but
you cannot use a positional notation after a named one.
Listing 1: record_selection.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Record_Selection is
4
5 type Months is
6 (January, February, March, April,
7 May, June, July, August, September,
8 October, November, December);
9
62 Chapter 6. Records
Introduction to Ada
33 Display_Date (Some_Day);
34 end Record_Selection;
Project: Courses.Intro_To_Ada.Records.Record_Selection
MD5: 79602cf4d011ba7423d07772b13e2b5a
Runtime output
As you can see in this example, we can use the dot notation in the expression D.Year or
Some_Day.Year to access the information stored in that component, as well as to mod-
ify this information in assignments. To be more specific, when we use D.Year in the call
to Put_Line, we're retrieving the information stored in that component. When we write
Some_Day.Year := 2001, we're overwriting the information that was previously stored in
the Year component of Some_Day.
6.4 Renaming
In previous chapters, we've discussed subprogram (page 27) and package (page 41) re-
naming. We can rename record components as well. Instead of writing the full component
selection using the dot notation, we can declare an alias that allows us to access the same
component. This is useful to simplify the implementation of a subprogram, for example.
We can rename record components by using the renames keyword in a variable declaration.
For example:
Some_Day : Date;
Y : Integer renames Some_Day.Year;
Here, Y is an alias, so that every time we using Y, we are really using the Year component
of Some_Day.
Let's look at a complete example:
Listing 2: dates.ads
1 package Dates is
2
3 type Months is
4 (January, February, March, April,
5 May, June, July, August, September,
6 October, November, December);
7
6.4. Renaming 63
Introduction to Ada
14 procedure Increase_Month
15 (Some_Day : in out Date);
16
17 procedure Display_Month
18 (Some_Day : Date);
19
20 end Dates;
Listing 3: dates.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
5 procedure Increase_Month
6 (Some_Day : in out Date)
7 is
8 -- Renaming components from
9 -- the Date record
10 M : Months renames Some_Day.Month;
11 Y : Integer renames Some_Day.Year;
12
27 procedure Display_Month
28 (Some_Day : Date)
29 is
30 -- Renaming components from
31 -- the Date record
32 M : Months renames Some_Day.Month;
33 Y : Integer renames Some_Day.Year;
34 begin
35 Put_Line ("Month: "
36 & Months'Image (M)
37 & ", Year:"
38 & Integer'Image (Y));
39 end Display_Month;
40
41 end Dates;
Listing 4: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Dates; use Dates;
3
4 procedure Main is
5 D : Date := (1, January, 2000);
(continues on next page)
64 Chapter 6. Records
Introduction to Ada
12 Display_Month (D);
13 end Main;
Project: Courses.Intro_To_Ada.Arrays.Record_Component_Renaming
MD5: 905390bd02b8417039052218800975a3
Runtime output
We apply renaming to two components of the Date record in the implementation of the In-
crease_Month procedure. Then, instead of directly using Some_Day.Month and Some_Day.
Year in the next operations, we simply use the renamed versions M and Y.
Note that, in the example above, we also rename Months'Succ — which is the function that
gives us the next month — to Next.
6.4. Renaming 65
Introduction to Ada
66 Chapter 6. Records
CHAPTER
SEVEN
ARRAYS
Listing 1: greet.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet is
4 type My_Int is range 0 .. 1000;
5 type Index is range 1 .. 5;
6
7 type My_Int_Array is
8 array (Index) of My_Int;
9 -- ^ Type of elements
10 -- ^ Bounds of the array
11 Arr : My_Int_Array := (2, 3, 5, 7, 11);
12 -- ^ Array literal
13 -- (aggregate)
14
15 V : My_Int;
16 begin
17 for I in Index loop
18 V := Arr (I);
19 -- ^ Take the Ith element
20 Put (My_Int'Image (V));
21 end loop;
22 New_Line;
23 end Greet;
Project: Courses.Intro_To_Ada.Arrays.Greet
MD5: ffdd2ba2322b0946dfcac3a55bce5270
Runtime output
2 3 5 7 11
The first point to note is that we specify the index type for the array, rather than its size.
Here we declared an integer type named Index ranging from 1 to 5, so each array instance
will have 5 elements, with the initial element at index 1 and the last element at index 5.
67
Introduction to Ada
Although this example used an integer type for the index, Ada is more general: any discrete
type is permitted to index an array, including Enum types (page 47). We will soon see what
that means.
Another point to note is that querying an element of the array at a given index uses the same
syntax as for function calls: that is, the array object followed by the index in parentheses.
Thus when you see an expression such as A (B), whether it is a function call or an array
subscript depends on what A refers to.
Finally, notice how we initialize the array with the (2, 3, 5, 7, 11) expression. This
is another kind of aggregate in Ada, and is in a sense a literal expression for an array, in
the same way that 3 is a literal expression for an integer. The notation is very powerful,
with a number of properties that we will introduce later. A detailed overview appears in the
notation of aggregate types (page 85).
Unrelated to arrays, the example also illustrated two procedures from Ada.Text_IO:
• Put, which displays a string without a terminating end of line
• New_Line, which outputs an end of line
Let's now delve into what it means to be able to use any discrete type to index into the
array.
® In other languages
Semantically, an array object in Ada is the entire data structure, and not simply a handle
or pointer. Unlike C and C++, there is no implicit equivalence between an array and a
pointer to its initial element.
Listing 2: array_bounds_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Array_Bounds_Example is
4 type My_Int is range 0 .. 1000;
5
10 type My_Int_Array is
11 array (Index) of My_Int;
12
Project: Courses.Intro_To_Ada.Arrays.Array_Bounds_Example
MD5: e5fe9e7b83055f3ae23dd890e29c22de
Runtime output
2 3 5 7 11
68 Chapter 7. Arrays
Introduction to Ada
One effect is that the bounds of an array can be any values. In the first example we con-
structed an array type whose first index is 1, but in the example above we declare an array
type whose first index is 11.
That's perfectly fine in Ada, and moreover since we use the index type as a range to iterate
over the array indices, the code using the array does not need to change.
That leads us to an important consequence with regard to code dealing with arrays. Since
the bounds can vary, you should not assume / hard-code specific bounds when iterating /
using arrays. That means the code above is good, because it uses the index type, but a for
loop as shown below is bad practice even though it works correctly:
for I in 11 .. 15 loop
Tab (I) := Tab (I) * 2;
end loop;
Since you can use any discrete type to index an array, enumeration types are permitted.
Listing 3: month_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Month_Example is
4 type Month_Duration is range 1 .. 31;
5 type Month is (Jan, Feb, Mar, Apr,
6 May, Jun, Jul, Aug,
7 Sep, Oct, Nov, Dec);
8
9 type My_Int_Array is
10 array (Month) of Month_Duration;
11 -- ^ Can use an enumeration type
12 -- as the index
13
Runtime output
JAN has 31 days.
FEB has 28 days.
MAR has 31 days.
(continues on next page)
7.2 Indexing
We have already seen the syntax for selecting elements of an array. There are however a
few more points to note.
First, as is true in general in Ada, the indexing operation is strongly typed. If you use a
value of the wrong type to index the array, you will get a compile-time error.
Listing 4: greet.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet is
4 type My_Int is range 0 .. 1000;
5
9 type My_Int_Array is
10 array (My_Index) of My_Int;
11
Project: Courses.Intro_To_Ada.Arrays.Greet_2
MD5: 54543017e4ec69d24bf9e43d507b50e6
Build output
70 Chapter 7. Arrays
Introduction to Ada
Second, arrays in Ada are bounds checked. This means that if you try to access an element
outside of the bounds of the array, you will get a run-time error instead of accessing random
memory as in unsafe languages.
Listing 5: greet.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Greet is
4 type My_Int is range 0 .. 1000;
5 type Index is range 1 .. 5;
6
7 type My_Int_Array is
8 array (Index) of My_Int;
9
Project: Courses.Intro_To_Ada.Arrays.Greet_3
MD5: 0102674d089be838f1dfbf0791d99fce
Build output
greet.adb:12:30: warning: static value out of range of type "Index" defined at␣
↪line 5 [enabled by default]
Runtime output
Listing 6: simple_array_bounds.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Simple_Array_Bounds is
4 type My_Int is range 0 .. 1000;
5
6 type My_Int_Array is
7 array (1 .. 5) of My_Int;
8 -- ^ Subtype of Integer
9
Runtime output
2 3 5 7 11
This example defines the range of the array via the range syntax, which specifies an anony-
mous subtype of Integer and uses it to index the array.
This means that the type of the index is Integer. Similarly, when you use an anonymous
range in a for loop as in the example above, the type of the iteration variable is also Integer,
so you can use I to index Tab.
You can also use a named subtype for the bounds for an array.
Listing 7: range_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Range_Example is
4 type My_Int is range 0 .. 1000;
5
6 type My_Int_Array is
7 array (1 .. 5) of My_Int;
8
72 Chapter 7. Arrays
Introduction to Ada
Project: Courses.Intro_To_Ada.Arrays.Range_Example
MD5: 8b0d7bf346cb59999dfd12dbaaf3e2a6
Runtime output
2 3 5 7 11
If you want more fine grained control, you can use the separate attributes 'First and
'Last.
Listing 8: array_attributes_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Array_Attributes_Example is
4 type My_Int is range 0 .. 1000;
5
6 type My_Int_Array is
7 array (1 .. 5) of My_Int;
8
9 Tab : My_Int_Array :=
10 (2, 3, 5, 7, 11);
11 begin
12 for I in Tab'First .. Tab'Last - 1 loop
13 -- ^ Iterate on every index
14 -- except the last
15 Put (My_Int'Image (Tab (I)));
16 end loop;
17 New_Line;
18 end Array_Attributes_Example;
Project: Courses.Intro_To_Ada.Arrays.Array_Attributes_Example
MD5: 95cc407c8aadd936e050fe3505e8fb46
Runtime output
2 3 5 7
The 'Range, 'First and 'Last attributes in these examples could also have been applied
to the array type name, and not just the array instances.
Although not illustrated in the above examples, another useful attribute for an array in-
stance A is A'Length, which is the number of elements that A contains.
It is legal and sometimes useful to have a "null array", which contains no elements. To get
this effect, define an index range whose upper bound is less than the lower bound.
Listing 9: unconstrained_array_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Unconstrained_Array_Example is
4 type Days is (Monday, Tuesday, Wednesday,
5 Thursday, Friday,
6 Saturday, Sunday);
7
8 type Workload_Type is
9 array (Days range <>) of Natural;
10 -- Indefinite array type
11 -- ^ Bounds are of type Days,
12 -- but not known
13
14 Workload : constant
15 Workload_Type (Monday .. Friday) :=
16 -- ^ Specify the bounds
17 -- when declaring
18 (Friday => 7, others => 8);
19 -- ^ Default value
20 -- ^ Specify element by name of index
21 begin
22 for I in Workload'Range loop
23 Put_Line (Integer'Image (Workload (I)));
24 end loop;
25 end Unconstrained_Array_Example;
Project: Courses.Intro_To_Ada.Arrays.Unconstrained_Array_Example
MD5: c84910e9b424cfabbbbe018ba0a6de59
Runtime output
8
8
8
8
7
The fact that the bounds of the array are not known is indicated by the Days range <>
syntax. Given a discrete type Discrete_Type, if we use Discrete_Type for the index in an
array type then Discrete_Type serves as the type of the index and comprises the range
of index values for each array instance.
If we define the index as Discrete_Type range <> then Discrete_Type serves as the type
of the index, but different array instances may have different bounds from this type.
An array type that is defined with the Discrete_Type range <> syntax for its index is
referred to as an unconstrained array type, and, as illustrated above, the bounds need to
be provided when an instance is created.
74 Chapter 7. Arrays
Introduction to Ada
The above example also shows other forms of the aggregate syntax. You can specify asso-
ciations by name, by giving the value of the index on the left side of an arrow association.
1 => 2 thus means "assign value 2 to the element at index 1 in my array". others => 8
means "assign value 8 to every element that wasn't previously assigned in this aggregate".
Á Attention
The so-called "box" notation (<>) is commonly used as a wildcard or placeholder in Ada.
You will often see it when the meaning is "what is expected here can be anything".
® In other languages
While unconstrained arrays in Ada might seem similar to variable length arrays in C,
they are in reality much more powerful, because they're truly first-class values in the
language. You can pass them as parameters to subprograms or return them from func-
tions, and they implicitly contain their bounds as part of their value. This means that it is
useless to pass the bounds or length of an array explicitly along with the array, because
they are accessible via the 'First, 'Last, 'Range and 'Length attributes explained
earlier.
Although different instances of the same unconstrained array type can have different
bounds, a specific instance has the same bounds throughout its lifetime. This allows Ada
to implement unconstrained arrays efficiently; instances can be stored on the stack and do
not require heap allocation as in languages like Java.
type String is
array (Positive range <>) of Character;
The only built-in feature Ada adds to make strings more ergonomic is custom literals, as we
can see in the example below.
b Hint
String literals are a syntactic sugar for aggregates, so that in the following example, A
and B have the same value.
Listing 10: string_literals.ads
1 package String_Literals is
2 -- Those two declarations are equivalent
3 A : String (1 .. 11) := "Hello World";
4 B : String (1 .. 11) :=
5 ('H', 'e', 'l', 'l', 'o', ' ',
6 'W', 'o', 'r', 'l', 'd');
7 end String_Literals;
Project: Courses.Intro_To_Ada.Arrays.String_Literals
MD5: 8e5871c8ead4ff8da643539857e23b30
3 procedure Greet is
4 Message : String (1 .. 11) := "dlroW olleH";
5 -- ^ Pre-defined array type.
6 -- Component type is Character
7 begin
8 for I in reverse Message'Range loop
9 -- ^ Iterate in reverse order
10 Put (Message (I));
11 end loop;
12 New_Line;
13 end Greet;
However, specifying the bounds of the object explicitly is a bit of a hassle; you have to
manually count the number of characters in the literal. Fortunately, Ada gives you an
easier way.
You can omit the bounds when creating an instance of an unconstrained array type if you
supply an initialization, since the bounds can be deduced from the initialization expression.
3 procedure Greet is
4 Message : constant String := "dlroW olleH";
5 -- ^ Bounds are automatically
6 -- computed from
7 -- initialization value
8 begin
9 for I in reverse Message'Range loop
10 Put (Message (I));
11 end loop;
12 New_Line;
13 end Greet;
Runtime output
Hello World
3 procedure Main is
4 type Integer_Array is
5 array (Natural range <>) of Integer;
6
76 Chapter 7. Arrays
Introduction to Ada
Á Attention
As you can see above, the standard String type in Ada is an array. As such, it shares the
advantages and drawbacks of arrays: a String value is stack allocated, it is accessed
efficiently, and its bounds are immutable.
If you want something akin to C++'s std::string, you can use Unbounded Strings
(page 243) from Ada's standard library. This type is more like a mutable, automatically
managed string buffer to which you can add content.
7.7 Restrictions
A very important point about arrays: bounds have to be known when instances are created.
It is for example illegal to do the following.
declare
A : String;
begin
A := "World";
end;
Also, while you of course can change the values of elements in an array, you cannot change
the array's bounds (and therefore its size) after it has been initialized. So this is also illegal:
declare
A : String := "Hello";
begin
A := "World"; -- OK: Same size
A := "Hello World"; -- Not OK: Different size
end;
Also, while you can expect a warning for this kind of error in very simple cases like this one,
it is impossible for a compiler to know in the general case if you are assigning a value of
the correct length, so this violation will generally result in a run-time error.
® Attention
While we will learn more about this later, it is important to know that arrays are
not the only types whose instances might be of unknown size at compile-time.
Such objects are said to be of an indefinite subtype, which means that the
subtype size is not known at compile time, but is dynamically computed (at
run time).
7.7. Restrictions 77
Introduction to Ada
3 procedure Indefinite_Subtypes is
4 function Get_Number return Integer is
5 begin
6 return Integer'Value (Get_Line);
7 end Get_Number;
8
9 A : String := "Hello";
10 -- Indefinite subtype
11
12 B : String (1 .. 5) := "Hello";
13 -- Definite subtype
14
15 C : String (1 .. Get_Number);
16 -- Indefinite subtype
17 -- (Get_Number's value is computed at
18 -- run-time)
19 begin
20 null;
21 end Indefinite_Subtypes;
Code block metadata
Project: Courses.Intro_To_Ada.Arrays.Indefinite_Subtypes
MD5: a24235838511a94879f74757421a28f0
3 procedure Main is
4
78 Chapter 7. Arrays
Introduction to Ada
23 begin
24 Put_Line ("First day is "
25 & Get_Day_Name (Days'First));
26 end Main;
Runtime output
First day is Monday
(This example is for illustrative purposes only. There is a built-in mechanism, the 'Image
attribute for scalar types, that returns the name (as a String) of any element of an enu-
meration type. For example Days'Image(Monday) is "MONDAY".)
® In other languages
Returning variable size objects in languages lacking a garbage collector is a bit compli-
cated implementation-wise, which is why C and C++ don't allow it, preferring to depend
on explicit dynamic allocation / free from the user.
The problem is that explicit storage management is unsafe as soon as you want to collect
unused memory. Ada's ability to return variable size objects will remove one use case for
dynamic allocation, and hence, remove one potential source of bugs from your programs.
Rust follows the C/C++ model, but with safe pointer semantics. However, dynamic al-
location is still used. Ada can benefit from a possible performance edge because it can
use any model.
3 procedure Show_Days is
4 type Days is (Monday, Tuesday, Wednesday,
5 Thursday, Friday,
6 Saturday, Sunday);
7
11 type Days_Name_Type is
12 array (Days) of Day_Name;
13 -- ^ Type of the index
14 -- ^ Type of the element.
15 -- Must be definite
(continues on next page)
Project: Courses.Intro_To_Ada.Arrays.Day_Name_2
MD5: bc66303091c084f66abde72ae59f55a9
Runtime output
Mo
Tu
We
Th
Fr
Sa
Su
3 procedure Main is
4 Buf : String := "Hello ...";
5
Project: Courses.Intro_To_Ada.Arrays.Slices
MD5: cdf582c6c9089658236f5c79b7be4c3f
Runtime output
80 Chapter 7. Arrays
Introduction to Ada
Hello Bob
Hi John
As we can see above, you can use a slice on the left side of an assignment, to replace only
part of an array.
A slice of an array is of the same type as the array, but has a different subtype, constrained
by the bounds of the slice.
Á Attention
Ada has multidimensional arrays11 , which are not covered in this course. Slices will only
work on one dimensional arrays.
7.11 Renaming
So far, we've seen that the following elements can be renamed: subprograms (page 27),
packages (page 41), and record components (page 63). We can also rename objects by
using the renames keyword. This allows for creating alternative names for these objects.
Let's look at an example:
5 Current_Temperature : Degree_Celsius;
6
7 end Measurements;
4 procedure Main is
5 subtype Degrees is
6 Measurements.Degree_Celsius;
7
8 T : Degrees
9 renames Measurements.Current_Temperature;
10 begin
11 T := 5.0;
12
17 T := T + 2.5;
18
7.11. Renaming 81
Introduction to Ada
Project: Courses.Intro_To_Ada.Arrays.Variable_Renaming
MD5: 4426aeaa364cb5cf10ff40e1bccb9757
Runtime output
5.00000E+00
5.00000E+00
7.50000E+00
7.50000E+00
9 type Color_Array is
10 array (Positive range <>) of Color;
11
14 end Colors;
82 Chapter 7. Arrays
Introduction to Ada
23 end Colors;
5 procedure Test_Reverse_Colors is
6
7 My_Colors : Color_Array (1 .. 5) :=
8 (Black, Red, Green, Blue, White);
9
10 begin
11 for C of My_Colors loop
12 Put_Line ("My_Color: "
13 & Color'Image (C));
14 end loop;
15
16 New_Line;
17 Put_Line ("Reversing My_Color...");
18 New_Line;
19 Reverse_It (My_Colors);
20
26 end Test_Reverse_Colors;
Project: Courses.Intro_To_Ada.Arrays.Reverse_Colors
MD5: cd9fd7f64d1ec8967e340d57fd7afc0a
Runtime output
My_Color: BLACK
My_Color: RED
My_Color: GREEN
My_Color: BLUE
My_Color: WHITE
Reversing My_Color...
My_Color: WHITE
My_Color: BLUE
My_Color: GREEN
My_Color: RED
My_Color: BLACK
In the example above, package Colors implements the procedure Reverse_It by declaring
new names for two positions of the array. The actual implementation becomes easy to read:
7.11. Renaming 83
Introduction to Ada
begin
Tmp := X_Left;
X_Left := X_Right;
X_Right := Tmp;
end;
begin
Tmp := X (I);
X (I) := X (X'Last +
X'First - I);
X (X'Last + X'First - I) := Tmp;
end;
84 Chapter 7. Arrays
CHAPTER
EIGHT
Listing 1: incorrect.ads
1 package Incorrect is
2 type Point is record
3 X, Y : Integer := 0;
4 end record;
5
Project: Courses.Intro_To_Ada.More_About_Types.Incorrect_Aggregate
MD5: 80a3475dece1c42cfb67b1d57b5bd464
Build output
There are a few shortcuts that you can use to make the notation more convenient:
• To specify the default value for a component, you can use the <> notation.
• You can use the | symbol to give several components the same value.
• You can use the others choice to refer to every component that has not yet been
specified, provided all those fields have the same type.
• You can use the range notation .. to refer to specify a contiguous sequence of indices
in an array.
However, note that as soon as you used a named association, all subsequent components
likewise need to be specified with named associations.
85
Introduction to Ada
Listing 2: points.ads
1 package Points is
2 type Point is record
3 X, Y : Integer := 0;
4 end record;
5
6 type Point_Array is
7 array (Positive range <>) of Point;
8
Project: Courses.Intro_To_Ada.More_About_Types.Points
MD5: 48ea183a42f203325ed6190fbd8493d9
Listing 3: pkg.ads
1 package Pkg is
2 function F (A : Integer) return Integer;
3 function F (A : Character) return Integer;
4 end Pkg;
Project: Courses.Intro_To_Ada.More_About_Types.Overloading
MD5: defae85228ee183b536af395d077e71e
Listing 4: pkg.ads
1 package Pkg is
2 type SSID is new Integer;
3
Listing 5: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Pkg; use Pkg;
3
4 procedure Main is
5 S : String := Convert (123_145_299);
6 -- ^ Valid, will choose the
7 -- proper Convert
8 begin
9 Put_Line (S);
10 end Main;
Á Attention
Note that overload resolution based on the type is allowed for both functions and enu-
meration literals in Ada - which is why you can have multiple enumeration literals with
the same name. Semantically, an enumeration literal is treated like a function that has
no parameters.
Listing 6: pkg.ads
1 package Pkg is
2 type SSID is new Integer;
3
Listing 7: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Pkg; use Pkg;
3
4 procedure Main is
5 S : String := Convert (123_145_299);
6 -- ^ Invalid, which convert
7 -- should we call?
8
16 I : SSID := 123_145_299;
17
Project: Courses.Intro_To_Ada.More_About_Types.Overloading_Error
MD5: 722660d8b692cde65a1c2b7800dd78c4
Syntactically the target of a qualified expression can be either any expression in parenthe-
ses, or an aggregate:
Listing 8: qual_expr.ads
1 package Qual_Expr is
2 type Point is record
3 A, B : Integer;
4 end record;
5
8 A : Integer := Integer'(12);
9 end Qual_Expr;
Project: Courses.Intro_To_Ada.More_About_Types.Qual_Expr
MD5: e71523eb441a28a4f6549d5f0418620a
This illustrates that qualified expressions are a convenient (and sometimes necessary) way
for the programmer to make the type of an expression explicit, for the compiler of course,
but also for other programmers.
Á Attention
While they look and feel similar, type conversions and qualified expressions are not the
same.
A qualified expression specifies the exact type that the target expression will be resolved
to, whereas a type conversion will try to convert the target and issue a run-time error if
the target value cannot be so converted.
Note that you can use a qualified expression to convert from one subtype to another,
with an exception raised if a constraint is violated.
X : Integer := Natural'(1);
Listing 9: character_example.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Character_Example is
4 type My_Char is ('a', 'b', 'c');
5 -- Our custom character type, an
6 -- enumeration type with 3 valid values.
7
8 C : Character;
9 -- ^ Built-in character type
10 -- (it's an enumeration type)
11
12 M : My_Char;
13 begin
14 C := '?';
15 -- ^ Character literal
16 -- (enumeration literal)
17
18 M := 'a';
19
20 C := 65;
21 -- ^ Invalid: 65 is not a
22 -- Character value
23
24 C := Character'Val (65);
25 -- Assign the character at
26 -- position 65 in the
27 -- enumeration (which is 'A')
28
29 M := C;
30 -- ^ Invalid: C is of type Character,
31 -- and M is a My_Char
32
33 M := 'd';
34 -- ^ Invalid: 'd' is not a valid
35 -- literal for type My_Char
36 end Character_Example;
Project: Courses.Intro_To_Ada.More_About_Types.Character_Example
MD5: e4c5a07dbe8745749056f8c110d69fa3
Build output
NINE
9.1 Overview
Pointers are a potentially dangerous construct, which conflicts with Ada's underlying phi-
losophy.
There are two ways in which Ada helps shield programmers from the dangers of pointers:
1. One approach, which we have already seen, is to provide alternative features so that
the programmer does not need to use pointers. Parameter modes, arrays, and varying
size types are all constructs that can replace typical pointer usages in C.
2. Second, Ada has made pointers as safe and restricted as possible, but allows "escape
hatches" when the programmer explicitly requests them and presumably will be exer-
cising such features with appropriate care.
Here is how you declare a simple pointer type, or access type, in Ada:
Listing 1: dates.ads
1 package Dates is
2 type Months is
3 (January, February, March, April,
4 May, June, July, August, September,
5 October, November, December);
6
Listing 2: access_types.ads
1 with Dates; use Dates;
2
3 package Access_Types is
4 -- Declare an access type
5 type Date_Acc is access Date;
6 -- ^ "Designated type"
7 -- ^ Date_Acc values
8 -- point to Date
9 -- objects
10
11 D : Date_Acc := null;
12 -- ^ Literal for
13 -- "access to nothing"
(continues on next page)
91
Introduction to Ada
Listing 3: access_types.ads
1 with Dates; use Dates;
2
3 package Access_Types is
4 -- Declare an access type
5 type Date_Acc is access Date;
6 type Date_Acc_2 is access Date;
7
8 D : Date_Acc := null;
9 D2 : Date_Acc_2 := D;
10 -- ^ Invalid! Different types
11 end Access_Types;
Build output
access_types.ads:9:24: error: expected type "Date_Acc_2" defined at line 6
access_types.ads:9:24: error: found type "Date_Acc" defined at line 5
gprbuild: *** compilation phase failed
® In other languages
In most other languages, pointer types are structurally, not nominally typed, like they
are in Ada, which means that two pointer types will be the same as long as they share
the same target type and accessibility rules.
Not so in Ada, which takes some time getting used to. A seemingly simple problem
is, if you want to have a canonical access to a type, where should it be declared? A
commonly used pattern is that if you need an access type to a specific type you "own",
you will declare it along with the type:
package Access_Types is
type Point is record
X, Y : Natural;
end record;
Listing 4: access_types.ads
1 with Dates; use Dates;
2
3 package Access_Types is
4 type Date_Acc is access Date;
5
If the type you want to allocate needs constraints, you can put them in the subtype indica-
tion, just as you would do in a variable declaration:
Listing 5: access_types.ads
1 with Dates; use Dates;
2
3 package Access_Types is
4 type String_Acc is access String;
5 -- ^
6 -- Access to unconstrained array type
7 Msg : String_Acc;
8 -- ^ Default value is null
9
10 Buffer : String_Acc :=
11 new String (1 .. 10);
12 -- ^ Constraint required
13 end Access_Types;
In some cases, though, allocating just by specifying the type is not ideal, so Ada also allows
you to initialize along with the allocation. This is done via the qualified expression syntax:
Listing 6: access_types.ads
1 with Dates; use Dates;
2
3 package Access_Types is
4 type Date_Acc is access Date;
5 type String_Acc is access String;
6
7 D : Date_Acc :=
8 new Date'(30, November, 2011);
(continues on next page)
9.3 Dereferencing
The last important piece of Ada's access type facility is how to get from an access value
to the object that is pointed to, that is, how to dereference the pointer. Dereferencing a
pointer uses the .all syntax in Ada, but is often not needed — in many cases, the access
value will be implicitly dereferenced for you:
Listing 7: access_types.ads
1 with Dates; use Dates;
2
3 package Access_Types is
4 type Date_Acc is access Date;
5
6 D : Date_Acc :=
7 new Date'(30, November, 2011);
8
Project: Courses.Intro_To_Ada.Access_Types.Access_Types
MD5: 5cd1c259da04010b0dc1b43e9bd93b55
Á Attention
The guideline in Ada is that most of the time you can avoid manual allocation, and you
should.
There are many ways to avoid manual allocation, some of which have been covered
(such as parameter modes). The language also provides library abstractions to avoid
pointers:
1. One is the use of containers (page 199). Containers help users avoid pointers,
because container memory is automatically managed.
2. A container to note in this context is the Indefinite holder13 . This container allows
you to store a value of an indefinite type such as String.
3. GNATCOLL has a library for smart pointers, called Refcount14 Those pointers' mem-
ory is automatically managed, so that when an allocated object has no more refer-
ences to it, the memory is automatically deallocated.
Listing 8: simple_list.ads
1 package Simple_List is
2 type Node;
3 -- This is an incomplete type declaration,
4 -- which is completed in the same
5 -- declarative region.
6
Project: Courses.Intro_To_Ada.Access_Types.Simple_List
MD5: 4929b89c1fc913da635fa02e48248271
In this example, the Node and Node_Acc types are mutually dependent.
13 http://www.ada-auth.org/standards/12rat/html/Rat12-8-5.html
14 https://github.com/AdaCore/gnatcoll-core/blob/master/src/gnatcoll-refcount.ads
TEN
Listing 1: runtime_length.ads
1 package Runtime_Length is
2 function Compute_Max_Len return Natural;
3 end Runtime_Length;
Listing 2: var_size_record.ads
1 with Runtime_Length; use Runtime_Length;
2
3 package Var_Size_Record is
4 Max_Len : constant Natural :=
5 Compute_Max_Len;
6 -- ^ Not known at compile time
7
8 type Items_Array is
9 array (Positive range <>) of Integer;
10
18 G : Growable_Stack;
19 end Var_Size_Record;
Project: Courses.Intro_To_Ada.More_About_Records.Var_Size_Record
MD5: 6fb0b3f2b685a72ec694640ce378f77c
It is completely fine to determine the size of your records at run time, but note that all
objects of this type will have the same size.
97
Introduction to Ada
Listing 3: var_size_record_2.ads
1 package Var_Size_Record_2 is
2 type Items_Array is
3 array (Positive range <>) of Integer;
4
Project: Courses.Intro_To_Ada.More_About_Records.Var_Size_Record_2
MD5: 0c2ffe41b7553984e1ef48a50386559f
Discriminants, in their simple forms, are constant: You cannot modify them once you have
initialized the object. This intuitively makes sense since they determine the size of the
object.
Also, they make a type indefinite: Whether or not the discriminant is used to specify the size
of an object, a type with a discriminant will be indefinite if the discriminant is not declared
with an initialization:
Listing 4: test_discriminants.ads
1 package Test_Discriminants is
2 type Point (X, Y : Natural) is record
3 null;
4 end record;
5
6 P : Point;
7 -- ERROR: Point is indefinite, so you
8 -- need to specify the discriminants
9 -- or give a default value
10
15 end Test_Discriminants;
Project: Courses.Intro_To_Ada.More_About_Records.Test_Discriminants
MD5: c3ec81ccae0d4144fe952ad99482be81
Build output
This also means that, in the example above, you cannot declare an array of Point values,
because the size of a Point is not known.
As mentioned in the example above, we could provide a default value for the discriminants,
so that we could legally declare Point values without specifying the discriminants. For the
example above, this is how it would look:
Listing 5: test_discriminants.ads
1 package Test_Discriminants is
2 type Point (X, Y : Natural := 0) is record
3 null;
4 end record;
5
6 P : Point;
7 -- We can now simply declare a "Point"
8 -- without further ado. In this case,
9 -- we're using the default values (0)
10 -- for X and Y.
11
16 end Test_Discriminants;
Project: Courses.Intro_To_Ada.More_About_Records.Test_Discriminants
MD5: 259f6cdf7fa857cc006dac6d1daedd73
Also note that, even though the Point type now has default discriminants, we can still
specify discriminants, as we're doing in the declarations of P2 and P3.
In most other respects discriminants behave like regular fields: You have to specify their
values in aggregates, as seen above, and you can access their values via the dot notation.
Listing 6: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
5 procedure Main is
6 procedure Print_Stack (G : Growable_Stack) is
7 begin
8 Put ("<Stack, items: [");
9 for I in G.Items'Range loop
10 exit when I > G.Len;
(continues on next page)
16 S : Growable_Stack :=
17 (Max_Len => 128,
18 Items => (1, 2, 3, 4, others => <>),
19 Len => 4);
20 begin
21 Print_Stack (S);
22 end Main;
Project: Courses.Intro_To_Ada.More_About_Records.Var_Size_Record_2
MD5: 4e8c102cd93dc5d8aa1b402589c5239b
Runtime output
® Note
In the examples above, we used a discriminant to determine the size of an array, but it
is not limited to that, and could be used, for example, to determine the size of a nested
discriminated record.
Listing 7: variant_record.ads
1 package Variant_Record is
2 -- Forward declaration of Expr
3 type Expr;
4
5 -- Access to a Expr
6 type Expr_Access is access Expr;
7
Project: Courses.Intro_To_Ada.More_About_Records.Variant_Record
MD5: af9c1edca3ed6b2d938249c7258806b1
The fields that are in a when branch will be only available when the value of the discriminant
is covered by the branch. In the example above, you will only be able to access the fields
Left and Right when the Kind is Bin_Op_Plus or Bin_Op_Minus.
If you try to access a field that is not valid for your record, a Constraint_Error will be
raised.
Listing 8: main.adb
1 with Variant_Record; use Variant_Record;
2
3 procedure Main is
4 E : Expr := (Num, 12);
5 begin
6 E.Left := new Expr'(Num, 15);
7 -- Will compile but fail at runtime
8 end Main;
Project: Courses.Intro_To_Ada.More_About_Records.Variant_Record
MD5: d157d5f96db0825b9376ba7fca9613ed
Build output
Runtime output
Listing 9: main.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
5 procedure Main is
6 function Eval_Expr (E : Expr) return Integer is
7 (case E.Kind is
8 when Bin_Op_Plus =>
9 Eval_Expr (E.Left.all)
(continues on next page)
16 E : Expr := (Bin_Op_Plus,
17 new Expr'(Bin_Op_Minus,
18 new Expr'(Num, 12),
19 new Expr'(Num, 15)),
20 new Expr'(Num, 3));
21 begin
22 Put_Line (Integer'Image (Eval_Expr (E)));
23 end Main;
Project: Courses.Intro_To_Ada.More_About_Records.Variant_Record
MD5: 807dbb921b44b3eaeaf1baf6ffe1afaa
Runtime output
® In other languages
Ada's variant records are very similar to Sum types in functional languages such as
OCaml or Haskell. A major difference is that the discriminant is a separate field in Ada,
whereas the 'tag' of a Sum type is kind of built in, and only accessible with pattern
matching.
There are other differences (you can have several discriminants in a variant record in
Ada). Nevertheless, they allow the same kind of type modeling as sum types in functional
languages.
Compared to C/C++ unions, Ada variant records are more powerful in what they allow,
and are also checked at run time, which makes them safer.
ELEVEN
FIXED-POINT TYPES
In this case, the delta and the digits will be used by the compiler to derive a range.
Several attributes are useful for dealing with decimal types:
In the example below, we declare two data types: T3_D3 and T6_D3. For both types, the
delta value is the same: 0.001.
Listing 1: decimal_fixed_point_types.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Decimal_Fixed_Point_Types is
4 type T3_D3 is delta 10.0 ** (-3) digits 3;
5 type T6_D3 is delta 10.0 ** (-3) digits 6;
6 begin
7 Put_Line ("The delta value of T3_D3 is "
8 & T3_D3'Image (T3_D3'Delta));
9 Put_Line ("The minimum value of T3_D3 is "
10 & T3_D3'Image (T3_D3'First));
11 Put_Line ("The maximum value of T3_D3 is "
12 & T3_D3'Image (T3_D3'Last));
13 New_Line;
14
103
Introduction to Ada
Project: Courses.Intro_To_Ada.Fixed_Point_Types.Decimal_Fixed_Point_Types
MD5: 6b1f6bfa555031b831aa872187c8bee9
Runtime output
When running the application, we see that the delta value of both types is indeed the same:
0.001. However, because T3_D3 is restricted to 3 digits, its range is -0.999 to 0.999. For
the T6_D3, we have defined a precision of 6 digits, so the range is -999.999 to 999.999.
Similar to the type definition using the range syntax, because we have an implicit range,
the compiled code will check that the variables contain values that are not out-of-range.
Also, if the result of a multiplication or division on decimal fixed-point types is smaller than
the delta value required for the context, the actual result will be zero. For example:
Listing 2: decimal_fixed_point_smaller.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Decimal_Fixed_Point_Smaller is
4 type T3_D3 is delta 10.0 ** (-3) digits 3;
5 type T6_D6 is delta 10.0 ** (-6) digits 6;
6 A : T3_D3 := T3_D3'Delta;
7 B : T3_D3 := 0.5;
8 C : T6_D6;
9 begin
10 Put_Line ("The value of A is "
11 & T3_D3'Image (A));
12
13 A := A * B;
14 Put_Line ("The value of A * B is "
15 & T3_D3'Image (A));
16
17 A := T3_D3'Delta;
18 C := A * B;
19 Put_Line ("The value of A * B is "
20 & T6_D6'Image (C));
21 end Decimal_Fixed_Point_Smaller;
Project: Courses.Intro_To_Ada.Fixed_Point_Types.Decimal_Fixed_Point_Smaller
MD5: 6b0242caa4a79f9b3447a304002e6a3b
Runtime output
In this example, the result of the operation 0.001 * 0.5 is 0.0005. Since this value is not
representable for the T3_D3 type because the delta value is 0.001, the actual value stored
in variable A is zero. However, accuracy is preserved during the arithmetic operations if the
target has sufficient precision, and the value displayed for C is 0.000500.
® Note
Ordinary fixed-point types can be thought of being closer to the actual representation on
the machine, since hardware support for decimal fixed-point arithmetic is not widespread
(rescalings by a power of ten), while ordinary fixed-point types make use of the available
integer shift instructions.
type <type-name> is
delta <delta-value>
range <lower-bound> .. <upper-bound>;
By default the compiler will choose a scale factor, or small, that is a power of 2 no greater
than <delta-value>.
For example, we may define a normalized range between -1.0 and 1.0 as following:
Listing 3: normalized_fixed_point_type.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Normalized_Fixed_Point_Type is
4 D : constant := 2.0 ** (-31);
5 type TQ31 is delta D range -1.0 .. 1.0 - D;
6 begin
7 Put_Line ("TQ31 requires "
8 & Integer'Image (TQ31'Size)
9 & " bits");
10 Put_Line ("The delta value of TQ31 is "
11 & TQ31'Image (TQ31'Delta));
12 Put_Line ("The minimum value of TQ31 is "
13 & TQ31'Image (TQ31'First));
14 Put_Line ("The maximum value of TQ31 is "
15 & TQ31'Image (TQ31'Last));
16 end Normalized_Fixed_Point_Type;
Project: Courses.Intro_To_Ada.Fixed_Point_Types.Normalized_Fixed_Point_Type
MD5: 778dde401c7ff3dd42938dccfe6cf9d3
Runtime output
In this example, we are defining a 32-bit fixed-point data type for our normalized range.
When running the application, we notice that the upper bound is close to one, but not
exact one. This is a typical effect of fixed-point data types — you can find more details in
this discussion about the Q format15 .
We may also rewrite this code with an exact type definition:
Listing 4: normalized_adapted_fixed_point_type.adb
1 procedure Normalized_Adapted_Fixed_Point_Type is
2 type TQ31 is
3 delta 2.0 ** (-31)
4 range -1.0 .. 1.0 - 2.0 ** (-31);
5 begin
6 null;
7 end Normalized_Adapted_Fixed_Point_Type;
Project: Courses.Intro_To_Ada.Fixed_Point_Types.Normalized_Adapted_Fixed_Point_Type
MD5: 3421800bb47b282d601a51d276944f62
Listing 5: custom_fixed_point_range.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Numerics; use Ada.Numerics;
3
4 procedure Custom_Fixed_Point_Range is
5 type T_Inv_Trig is
6 delta 2.0 ** (-15) * Pi
7 range -Pi / 2.0 .. Pi / 2.0;
8 begin
9 Put_Line ("T_Inv_Trig requires "
10 & Integer'Image (T_Inv_Trig'Size)
11 & " bits");
12 Put_Line ("Delta value of T_Inv_Trig: "
13 & T_Inv_Trig'Image
14 (T_Inv_Trig'Delta));
15 Put_Line ("Minimum value of T_Inv_Trig: "
16 & T_Inv_Trig'Image
17 (T_Inv_Trig'First));
18 Put_Line ("Maximum value of T_Inv_Trig: "
19 & T_Inv_Trig'Image
20 (T_Inv_Trig'Last));
21 end Custom_Fixed_Point_Range;
Project: Courses.Intro_To_Ada.Fixed_Point_Types.Custom_Fixed_Point_Range
MD5: a3e6c549cb1070aa285857ae8813de27
Runtime output
In this example, we are defining a 16-bit type called T_Inv_Trig, which has a range from
-π/2 to π/2.
All standard operations are available for fixed-point types. For example:
Listing 6: fixed_point_op.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Fixed_Point_Op is
4 type TQ31 is
5 delta 2.0 ** (-31)
6 range -1.0 .. 1.0 - 2.0 ** (-31);
7
8 A, B, R : TQ31;
9 begin
10 A := 0.25;
11 B := 0.50;
12 R := A + B;
13 Put_Line ("R is " & TQ31'Image (R));
14 end Fixed_Point_Op;
Project: Courses.Intro_To_Ada.Fixed_Point_Types.Fixed_Point_Op
MD5: cad218b70b7fb0621468027a807431b1
Runtime output
R is 0.7500000000
type Angle is
delta 1.0/3600.0
range 0.0 .. 360.0 - 1.0 / 3600.0;
for Angle'Small use Angle'Delta;
TWELVE
PRIVACY
One of the main principles of modular programming, as well as object oriented program-
ming, is encapsulation16 .
Encapsulation, briefly, is the concept that the implementer of a piece of software will dis-
tinguish between the code's public interface and its private implementation.
This is not only applicable to software libraries but wherever abstraction is used.
In Ada, the granularity of encapsulation is a bit different from most object-oriented lan-
guages, because privacy is generally specified at the package level.
Listing 1: encapsulate.ads
1 package Encapsulate is
2 procedure Hello;
3
4 private
5
6 procedure Hello2;
7 -- Not visible from external units
8 end Encapsulate;
Listing 2: encapsulate.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
5 procedure Hello is
6 begin
7 Put_Line ("Hello");
8 end Hello;
9
10 procedure Hello2 is
11 begin
12 Put_Line ("Hello #2");
13 end Hello2;
14
15 end Encapsulate;
16 https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)
109
Introduction to Ada
Listing 3: main.adb
1 with Encapsulate;
2
3 procedure Main is
4 begin
5 Encapsulate.Hello;
6 Encapsulate.Hello2;
7 -- Invalid: Hello2 is not visible
8 end Main;
Build output
main.adb:6:15: error: "Hello2" is not a visible entity of "Encapsulate"
gprbuild: *** compilation phase failed
Listing 4: stacks.ads
1 package Stacks is
2 type Stack is private;
3 -- Declare a private type: You cannot depend
4 -- on its implementation. You can only assign
5 -- and test for equality.
6
13 subtype Stack_Index is
14 Natural range 1 .. 10;
15
16 type Content_Type is
17 array (Stack_Index) of Natural;
18
Listing 5: stacks.adb
1 package body Stacks is
2
17 end Stacks;
Project: Courses.Intro_To_Ada.Privacy.Stacks
MD5: 364df7c6806af4a1bc957c2c2d53b2cc
In the above example, we define a stack type in the public part (known as the visible part
of the package spec in Ada), but the exact representation of that type is private.
Then, in the private part, we define the representation of that type. We can also declare
other types that will be used as helpers for our main public type. This is useful since declar-
ing helper types is common in Ada.
A few words about terminology:
• The Stack type as viewed from the public part is called the partial view of the type.
This is what clients have access to.
• The Stack type as viewed from the private part or the body of the package is called
the full view of the type. This is what implementers have access to.
From the point of view of the client (the with'ing unit), only the public (visible) part is im-
portant, and the private part could as well not exist. It makes it very easy to read linearly
the part of the package that is important for you.
-- Example of use
with Stacks; use Stacks;
procedure Test_Stack is
S : Stack;
Res : Integer;
begin
Push (S, 5);
Push (S, 7);
Pop (S, Res);
end Test_Stack;
Listing 6: stacks.ads
1 package Stacks is
2 type Stack is limited private;
3 -- Limited type. Cannot assign nor compare.
4
13 type Content_Type is
14 array (Stack_Index) of Natural;
15
Listing 7: stacks.adb
1 package body Stacks is
2
17 end Stacks;
Listing 8: main.adb
1 with Stacks; use Stacks;
2
3 procedure Main is
4 S, S2 : Stack;
5 begin
6 S := S2;
7 -- Illegal: S is limited.
8 end Main;
Project: Courses.Intro_To_Ada.Privacy.Limited_Stacks
MD5: 811343b46f20ac6af5e1bf26561f8d8d
Build output
This is useful because, for example, for some data types the built-in assignment operation
might be incorrect (for example when a deep copy is required).
Ada does allow you to overload the comparison operators = and /= for limited types (and
to override the built-in declarations for non-limited types).
Ada also allows you to implement special semantics for assignment via controlled types17 .
However, in some cases assignment is simply inappropriate; one example is the File_Type
from the Ada.Text_IO package, which is declared as a limited type and thus attempts to
assign one file to another would be detected as illegal.
Part of a child package Access to the private part of its parent's specification
Specification: public part
Specification: private part ✓
Body ✓
The rest of this section shows examples of how this access to private information actually
works for child packages.
Let's first look at an example where the body of a child package P.C has access to the private
part of the specification of its parent P. We've seen, in a previous source-code example,
17 http://www.ada-auth.org/standards/12rm/html/RM-7-6.html
that the Hello2 procedure declared in the private part of the Encapsulate package cannot
be used in the Main procedure, since it's not visible there. This limitation doesn't apply,
however, for parts of the child packages of the Encapsulate package. In fact, the body of
its child package Encapsulate.Child has access to the Hello2 procedure and can call it
there, as you can see in the implementation of the Hello3 procedure of the Child package:
Listing 9: encapsulate.ads
1 package Encapsulate is
2 procedure Hello;
3
4 private
5
6 procedure Hello2;
7 -- Not visible from external units
8 -- But visible in child packages
9 end Encapsulate;
5 procedure Hello is
6 begin
7 Put_Line ("Hello");
8 end Hello;
9
10 procedure Hello2 is
11 begin
12 Put_Line ("Hello #2");
13 end Hello2;
14
15 end Encapsulate;
3 procedure Hello3;
4
5 end Encapsulate.Child;
5 procedure Hello3 is
6 begin
7 -- Using private procedure Hello2
8 -- from the parent package
9 Hello2;
10 Put_Line ("Hello #3");
11 end Hello3;
12
13 end Encapsulate.Child;
3 procedure Main is
4 begin
5 Encapsulate.Child.Hello3;
6 end Main;
Project: Courses.Intro_To_Ada.Privacy.Encapsulate_Child
MD5: 1533f43eee8f8b4d14c9b2101f42f13a
Runtime output
Hello #2
Hello #3
The same mechanism applies to types declared in the private part of a parent package. For
instance, the body of a child package can access components of a record declared in the
private part of its parent package. Let's look at an example:
5 private
6
11 end My_Types;
5 end My_Types.Ops;
11 end My_Types.Ops;
6 procedure Main is
7 E : Priv_Rec;
8 begin
9 Put_Line ("Presenting information:");
10
17 Display (E);
18 end Main;
Project: Courses.Intro_To_Ada.Privacy.Private_Type_Child
MD5: 9960611460bc1190b30949eca08fc02b
Runtime output
Presenting information:
Priv_Rec.Number: 42
In this example, we don't have access to the Number component of the record type Priv_Rec
in the Main procedure. You can see this in the call to Put_Line that has been commented-
out in the implementation of Main. Trying to access the Number component there would
trigger a compilation error. But we do have access to this component in the body of the
My_Types.Ops package, since it's a child package of the My_Types package. Therefore,
Ops's body has access to the declaration of the Priv_Rec type — which is in the private
part of its parent, the My_Types package. For this reason, the same call to Put_Line that
would trigger a compilation error in the Main procedure works fine in the Display procedure
of the My_Types.Ops package.
This kind of privacy rules for child packages allows for extending the functionality of a parent
package and, at the same time, retain its encapsulation.
As we mentioned previously, in addition to the package body, the private part of the speci-
fication of a child package P.C also has access to the private part of the specification of its
parent P. Let's look at an example where we declare an object of private type Priv_Rec in
the private part of the child package My_Types.Child and initialize the Number component
of the Priv_Rec record directly:
package My_Types.Child is
private
end My_Types.Ops;
package My_Types.Child is
end My_Types.Ops;
The declaration above triggers a compilation error, since type Priv_Rec is private. Because
the public part of My_Types.Child is also visible outside the child package, Ada cannot
allow accessing private information in this part of the specification.
THIRTEEN
GENERICS
13.1 Introduction
Generics are used for metaprogramming in Ada. They are useful for abstract algorithms
that share common properties with each other.
Either a subprogram or a package can be generic. A generic is declared by using the key-
word generic. For example:
Listing 1: operator.ads
1 generic
2 type T is private;
3 -- Declaration of formal types and objects
4 -- Below, we could use one of the following:
5 -- <procedure | function | package>
6 procedure Operator (Dummy : in out T);
Listing 2: operator.adb
1 procedure Operator (Dummy : in out T) is
2 begin
3 null;
4 end Operator;
Project: Courses.Intro_To_Ada.Generics.Show_Simple_Generic
MD5: 1321d437043dafdb725fad416e654318
Listing 3: set.ads
1 generic
2 type T is private;
3 -- T is a formal type that indicates that
4 -- any type can be used, possibly a numeric
5 -- type or possibly even a record type.
6 procedure Set (Dummy : T);
119
Introduction to Ada
Listing 4: set.adb
1 procedure Set (Dummy : T) is
2 begin
3 null;
4 end Set;
Project: Courses.Intro_To_Ada.Generics.Show_Formal_Type_Declaration
MD5: 668156f66b2479c4932d18b5ad35deba
The declaration of T as private indicates that you can map any definite type to it. But you
can also restrict the declaration to allow only some types to be mapped to that formal type.
Here are some examples:
Listing 5: set.ads
1 generic
2 type T is private;
3 X : in out T;
4 -- X can be used in the Set procedure
5 procedure Set (E : T);
Listing 6: set.adb
1 procedure Set (E : T) is
2 pragma Unreferenced (E, X);
3 begin
4 null;
5 end Set;
Project: Courses.Intro_To_Ada.Generics.Show_Formal_Object_Declaration
MD5: 1b88bc0e5b8f48a35394966e6af07ac0
Formal objects can be either input parameters or specified using the in out mode.
Listing 7: set.ads
1 generic
2 type T is private;
3 X : in out T;
4 procedure Set (E : T);
Listing 8: set.adb
1 procedure Set (E : T) is
2 -- Body definition: "generic" keyword
3 -- is not used
4 begin
5 X := E;
6 end Set;
Project: Courses.Intro_To_Ada.Generics.Show_Generic_Body_Definition
MD5: de611ef77b528543fd6bad82c53857f7
Listing 9: set.ads
1 generic
2 type T is private;
3 X : in out T;
4 -- X can be used in the Set procedure
5 procedure Set (E : T);
4 procedure Show_Generic_Instantiation is
5
6 Main : Integer := 0;
7 Current : Integer;
8
20 begin
21 Current := 10;
22
23 Set_Main (Current);
24 Put_Line ("Value of Main is "
25 & Integer'Image (Main));
26 end Show_Generic_Instantiation;
Runtime output
Value of Main is 10
In the example above, we instantiate the procedure Set by mapping the formal parameters
T and X to actual existing elements, in this case the Integer type and the Main variable.
10 Invalid_Element : exception;
11
12 private
13 Value : T;
14 Valid : Boolean := False;
15 end Element;
3 procedure Set (E : T) is
4 begin
5 Value := E;
6 Valid := True;
(continues on next page)
9 procedure Reset is
10 begin
11 Valid := False;
12 end Reset;
13
4 procedure Show_Generic_Package is
5
8 procedure Display_Initialized is
9 begin
10 if I.Is_Valid then
11 Put_Line ("Value is initialized");
12 else
13 Put_Line ("Value is not initialized");
14 end if;
15 end Display_Initialized;
16
17 begin
18 Display_Initialized;
19
20 Put_Line ("Initializing...");
21 I.Set (5);
22 Display_Initialized;
23 Put_Line ("Value is now set to "
24 & Integer'Image (I.Get));
25
26 Put_Line ("Resetting...");
27 I.Reset;
28 Display_Initialized;
29
30 end Show_Generic_Package;
Runtime output
Value is not initialized
Initializing...
Value is initialized
(continues on next page)
In the example above, we created a simple container named Element, with just one single
element. This container tracks whether the element has been initialized or not.
After writing the package definition, we create the instance I of the Element. We use the
instance by calling the package subprograms (Set, Reset, and Get).
3 procedure Show_Formal_Subprogram is
4
5 A, B : Integer;
6
Project: Courses.Intro_To_Ada.Generics.Show_Formal_Subprogram
MD5: 1c463a47e9ce56b5afbca1da6acd116d
Runtime output
with Ada.Text_IO;
You can use it directly with any object of floating-point type. For example:
3 procedure Show_Float_Text_IO is
4 X : constant Float := 2.5;
5
6 use Ada.Float_Text_IO;
7 begin
8 Put (X);
9 end Show_Float_Text_IO;
Project: Courses.Intro_To_Ada.Generics.Show_Float_Text_IO
MD5: 7cc9b547ef301a2071e9fb65caa4631b
Runtime output
2.50000E+00
Instantiating generic I/O packages can be useful for derived types. For example, let's create
a new type Price that must be displayed with two decimal digits after the point, and no
exponent.
3 procedure Show_Float_IO_Inst is
4
10 P : Price;
11 begin
12 -- Set to zero => don't display exponent
13 Price_IO.Default_Exp := 0;
14
15 P := 2.5;
16 Price_IO.Put (P);
17 New_Line;
18
19 P := 5.75;
20 Price_IO.Put (P);
21 New_Line;
22 end Show_Float_IO_Inst;
Project: Courses.Intro_To_Ada.Generics.Show_Float_IO_Inst
MD5: 583c761421d7fdb812dd2a183b676bae
Runtime output
2.50
5.75
By adjusting Default_Exp from the Price_IO instance to remove the exponent, we can
control how variables of Price type are displayed. Just as a side note, we could also have
written:
-- [...]
begin
Price_IO.Default_Aft := 2;
Price_IO.Default_Exp := 0;
In this case, we're ajusting Default_Aft, too, to get two decimal digits after the point when
calling Put.
In addition to the generic Float_IO package, the following generic packages are available
from Ada.Text_IO:
• Enumeration_IO for enumeration types;
• Integer_IO for integer types;
• Modular_IO for modular types;
• Fixed_IO for fixed-point types;
3 procedure Show_Decimal_IO_Inst is
4
10 P : Price;
11 begin
12 Price_IO.Default_Exp := 0;
13
14 P := 2.5;
15 Price_IO.Put (P);
16 New_Line;
17
18 P := 5.75;
19 Price_IO.Put (P);
20 New_Line;
21 end Show_Decimal_IO_Inst;
Runtime output
2.50
5.75
17 private
18
19 type Stack_Array is
20 array (Natural range <>) of T;
21
22 Min : constant := 1;
23
29 end Stacks;
32 end Stacks;
4 procedure Show_Stack is
5
11 Values : Integer_Stacks.Stack;
12
13 begin
14 Push (Values, 10);
15 Push (Values, 20);
16
Project: Courses.Intro_To_Ada.Generics.Show_Stack
MD5: ee112d395552c1a02d211b9e5425dc71
Runtime output
In this example, we first create a generic stack package (Stacks) and then instantiate it to
create a stack of up to 10 integer values.
10 end Colors;
4 procedure Test_Non_Generic_Swap_Colors is
(continues on next page)
18 New_Line;
19 Put_Line ("Swapping A and C...");
20 New_Line;
21 Swap_Colors (A, C);
22
Project: Courses.Intro_To_Ada.Generics.Test_Non_Generic_Swap_Colors
MD5: 4d1cf826a1676c3750a8aabd484ac71f
Runtime output
Value of A is BLUE
Value of B is WHITE
Value of C is RED
Value of A is RED
Value of B is WHITE
Value of C is BLUE
In this example, Swap_Colors can only be used for the Color type. However, this algorithm
can theoretically be used for any type, whether an enumeration type or a complex record
type with many elements. The algorithm itself is the same: it's only the type that differs.
If, for example, we want to swap variables of Integer type, we don't want to duplicate the
implementation. Therefore, such an algorithm is a perfect candidate for abstraction using
generics.
In the example below, we create a generic version of Swap_Colors and name it
Generic_Swap. This generic version can operate on any type due to the declaration of
formal type T.
3 package Colors is
4
11 end Colors;
4 procedure Test_Swap_Colors is
5 A, B, C : Color;
6 begin
7 A := Blue;
8 B := White;
9 C := Red;
10
18 New_Line;
19 Put_Line ("Swapping A and C...");
20 New_Line;
21 Swap_Colors (A, C);
22
Project: Courses.Intro_To_Ada.Generics.Test_Swap_Colors
MD5: a5d94a40bd9d1c6736cc873f8b58e867
Runtime output
Value of A is BLUE
Value of B is WHITE
Value of C is RED
Value of A is RED
Value of B is WHITE
Value of C is BLUE
As we can see in the example, we can create the same Swap_Colors procedure as we had
in the non-generic version of the algorithm by declaring it as an instance of the generic
Generic_Swap procedure. We specify that the generic T type will be mapped to the Color
type by passing it as an argument to the Generic_Swap instantiation.
6 type Color_Array is
7 array (Integer range <>) of Color;
8
11 end Colors;
22 end Colors;
4 procedure Test_Non_Generic_Reverse_Colors is
5
6 My_Colors : Color_Array (1 .. 5) :=
7 (Black, Red, Green, Blue, White);
8
9 begin
10 for C of My_Colors loop
11 Put_Line ("My_Color: " & Color'Image (C));
12 end loop;
13
14 New_Line;
15 Put_Line ("Reversing My_Color...");
16 New_Line;
17 Reverse_It (My_Colors);
18
23 end Test_Non_Generic_Reverse_Colors;
Project: Courses.Intro_To_Ada.Generics.Test_Non_Generic_Reverse_Colors
MD5: 9b3a489d0bc0ecd79de6ba99fd7cd44f
Runtime output
My_Color: BLACK
My_Color: RED
My_Color: GREEN
My_Color: BLUE
My_Color: WHITE
Reversing My_Color...
My_Color: WHITE
My_Color: BLUE
My_Color: GREEN
My_Color: RED
My_Color: BLACK
The procedure Reverse_It takes an array of colors, starts by swapping the first and last
elements of the array, and continues doing that with successive elements until it reaches
the middle of array. At that point, the entire array has been reversed, as we see from the
output of the test program.
To abstract this procedure, we declare formal types for three components of the algorithm:
• the elements of the array (Color type in the example)
• the range used for the array (Integer range in the example)
• the actual array type (Color_Array type in the example)
This is a generic version of the algorithm:
3 package Colors is
4
8 type Color_Array is
9 array (Integer range <>) of Color;
10
16 end Colors;
4 procedure Test_Reverse_Colors is
5
6 My_Colors : Color_Array (1 .. 5) :=
7 (Black, Red, Green, Blue, White);
8
9 begin
10 for C of My_Colors loop
(continues on next page)
15 New_Line;
16 Put_Line ("Reversing My_Color...");
17 New_Line;
18 Reverse_It (My_Colors);
19
25 end Test_Reverse_Colors;
Runtime output
My_Color: BLACK
My_Color: RED
My_Color: GREEN
My_Color: BLUE
My_Color: WHITE
Reversing My_Color...
My_Color: WHITE
My_Color: BLUE
My_Color: GREEN
My_Color: RED
My_Color: BLACK
Here is a version of the test application making use of the generic Perform_Test procedure:
9 New_Line;
10 Put_Line ("Testing " & S & "...");
11 New_Line;
12 Test (X);
13
14 for C of X loop
15 Put_Line (S & ": " & Image (C));
(continues on next page)
3 package Colors is
4
8 type Color_Array is
9 array (Integer range <>) of Color;
10
16 end Colors;
4 procedure Test_Reverse_Colors is
5
14 My_Colors : Color_Array (1 .. 5) :=
15 (Black, Red, Green, Blue, White);
16
17 begin
18 Perform_Test_Reverse_It (My_Colors);
19 end Test_Reverse_Colors;
Project: Courses.Intro_To_Ada.Generics.Test_Reverse_Colors_2
MD5: 04640309f4f7e9f8bcff137d1a6f8733
Runtime output
My_Color: BLACK
My_Color: RED
My_Color: GREEN
My_Color: BLUE
My_Color: WHITE
Testing My_Color...
My_Color: WHITE
(continues on next page)
FOURTEEN
EXCEPTIONS
Ada uses exceptions for error handling. Unlike many other languages, Ada speaks about
raising, not throwing, an exception and handling, not catching, an exception.
Listing 1: exceptions.ads
1 package Exceptions is
2 My_Except : exception;
3 -- Like an object. *NOT* a type !
4 end Exceptions;
Project: Courses.Intro_To_Ada.Exceptions.Show_Exception
MD5: 6201faeca9b029c790023856d2c8c419
Even though they're objects, you're going to use each declared exception object as a "kind"
or "family" of exceptions. Ada does not require that a subprogram declare every exception
it can potentially raise.
Listing 2: main.adb
1 with Exceptions; use Exceptions;
2
3 procedure Main is
4 begin
5 raise My_Except;
6 -- Execution of current control flow
7 -- abandoned; an exception of kind
8 -- "My_Except" will bubble up until it
9 -- is caught.
10 end Main;
Project: Courses.Intro_To_Ada.Exceptions.Show_Exception
MD5: 24b40ae1509722adf51c3dd0d3ea4fbe
139
Introduction to Ada
Runtime output
Listing 3: main.adb
1 with Exceptions; use Exceptions;
2
3 procedure Main is
4 begin
5 raise My_Except with "My exception message";
6 -- Execution of current control flow
7 -- abandoned; an exception of kind
8 -- "My_Except" with associated string will
9 -- bubble up until it is caught.
10 end Main;
Project: Courses.Intro_To_Ada.Exceptions.Show_Exception
MD5: 279299c9703c3ed4e51fdd7c3a5e1392
Runtime output
Listing 4: open_file.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Exceptions; use Ada.Exceptions;
3
4 procedure Open_File is
5 File : File_Type;
6 begin
7 -- Block (sequence of statements)
8 begin
9 Open (File, In_File, "input.txt");
10 exception
11 when E : Name_Error =>
12 -- ^ Exception to be handled
13 Put ("Cannot open input file : ");
14 Put_Line (Exception_Message (E));
15 raise;
16 -- Reraise current occurence
17 end;
18 end Open_File;
Project: Courses.Intro_To_Ada.Exceptions.Show_Exception_Handling
MD5: 4ea1d5da684a6d7d7ee32908810e9c8f
Runtime output
In the example above, we're using the Exception_Message function from the Ada.
Exceptions package. This function returns the message associated with the exception
as a string.
You don't need to introduce a block just to handle an exception: you can add it to the
statements block of your current subprogram:
Listing 5: open_file.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Exceptions; use Ada.Exceptions;
3
4 procedure Open_File is
5 File : File_Type;
6 begin
7 Open (File, In_File, "input.txt");
8 -- Exception block can be added to any block
9 exception
10 when Name_Error =>
11 Put ("Cannot open input file");
12 end Open_File;
Project: Courses.Intro_To_Ada.Exceptions.Show_Exception_Message
MD5: 838e87ae416b3a717901cdc00eb71b40
Runtime output
® Attention
Exception handlers have an important restriction that you need to be careful about:
Exceptions raised in the declarative section are not caught by the handlers of that block.
So for example, in the following code, the exception will not be caught.
Listing 6: be_careful.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Exceptions; use Ada.Exceptions;
3
4 procedure Be_Careful is
5 function Dangerous return Integer is
6 begin
7 raise Constraint_Error;
8 return 42;
9 end Dangerous;
10
11 begin
12 declare
13 A : Integer := Dangerous;
14 begin
15 Put_Line (Integer'Image (A));
16 exception
17 when Constraint_Error =>
18 Put_Line ("error!");
19 end;
20 end Be_Careful;
Code block metadata
Project: Courses.Intro_To_Ada.Exceptions.Be_Careful
MD5: 6ea8a214bbbaca09d7444136d069e782
Runtime output
This is also the case for the top-level exception block that is part of the current subpro-
gram.
FIFTEEN
TASKING
Tasks and protected objects allow the implementation of concurrency in Ada. The following
sections explain these concepts in more detail.
15.1 Tasks
A task can be thought as an application that runs concurrently with the main application.
In other programming languages, a task might be called a thread18 , and tasking might be
called multithreading19 .
Tasks may synchronize with the main application but may also process information com-
pletely independently from the main application. Here we show how this is accomplished.
Listing 1: show_simple_task.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Simple_Task is
4 task T;
5
6 task body T is
7 begin
8 Put_Line ("In task T");
9 end T;
10 begin
11 Put_Line ("In main");
12 end Show_Simple_Task;
Project: Courses.Intro_To_Ada.Tasking.Show_Simple_Task
MD5: b17d9b35b4b2b53bc59776749e1be219
Runtime output
In task T
In main
18 https://en.wikipedia.org/wiki/Thread_(computing)
19 https://en.wikipedia.org/wiki/Thread_(computing)#Multithreading
143
Introduction to Ada
Here, we're declaring and implementing the task T. As soon as the main application starts,
task T starts automatically — it's not necessary to manually start this task. By running the
application above, we can see that both calls to Put_Line are performed.
Note that:
• The main application is itself a task (the main or “environment” task).
– In this example, the subprogram Show_Simple_Task is the main task of the appli-
cation.
• Task T is a subtask.
– Each subtask has a master, which represents the program construct in which the
subtask is declared. In this case, the main subprogram Show_Simple_Task is T 's
master.
– The master construct is executed by some enclosing task, which we will refer to
as the "master task" of the subtask.
• The number of tasks is not limited to one: we could include a task T2 in the example
above.
– This task also starts automatically and runs concurrently with both task T and the
main task. For example:
Listing 2: show_simple_tasks.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Simple_Tasks is
4 task T;
5 task T2;
6
7 task body T is
8 begin
9 Put_Line ("In task T");
10 end T;
11
12 task body T2 is
13 begin
14 Put_Line ("In task T2");
15 end T2;
16
17 begin
18 Put_Line ("In main");
19 end Show_Simple_Tasks;
Project: Courses.Intro_To_Ada.Tasking.Multiple_Simple_Task
MD5: 5e24b797e742bec306ad498f4f40d2b4
Runtime output
In task T
In task T2
In main
finished before it allows itself to complete. In other words, this waiting process provides
synchronization between the master task and its subtasks. After this synchronization, the
master construct will complete. For example:
Listing 3: show_simple_sync.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Simple_Sync is
4 task T;
5 task body T is
6 begin
7 for I in 1 .. 10 loop
8 Put_Line ("hello");
9 end loop;
10 end T;
11 begin
12 null;
13 -- Will wait here until all tasks
14 -- have terminated
15 end Show_Simple_Sync;
Project: Courses.Intro_To_Ada.Tasking.Show_Simple_Sync
MD5: 84afce465854f99f8cbe0b57714d8a5f
Runtime output
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
The same mechanism is used for other subprograms that contain subtasks: the subprogram
execution will wait for its subtasks to finish. So this mechanism is not limited to the main
subprogram and also applies to any subprogram called by the main subprogram, directly
or indirectly.
Synchronization also occurs if we move the task to a separate package. In the example
below, we declare a task T in the package Simple_Sync_Pkg.
Listing 4: simple_sync_pkg.ads
1 package Simple_Sync_Pkg is
2 task T;
3 end Simple_Sync_Pkg;
Project: Courses.Intro_To_Ada.Tasking.Simple_Sync_Pkg
MD5: 2f9be044d04994240970f150e2293d5e
Listing 5: simple_sync_pkg.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
Project: Courses.Intro_To_Ada.Tasking.Simple_Sync_Pkg
MD5: b668451e4fb10e802f619889bcd743ff
Because the package is with'ed by the main procedure, the task T defined in the package
will become a subtask of the main task. For example:
Listing 6: test_simple_sync_pkg.adb
1 with Simple_Sync_Pkg;
2
3 procedure Test_Simple_Sync_Pkg is
4 begin
5 null;
6 -- Will wait here until all tasks
7 -- have terminated
8 end Test_Simple_Sync_Pkg;
Project: Courses.Intro_To_Ada.Tasking.Simple_Sync_Pkg
MD5: e51565b91767ce198496ef3e9c582ac8
Runtime output
hello
hello
hello
hello
hello
hello
hello
hello
hello
hello
As soon as the main subprogram returns, the main task synchronizes with any subtasks
spawned by packages T from Simple_Sync_Pkg before finally terminating.
15.1.3 Delay
We can introduce a delay by using the keyword delay. This puts the current task to sleep
for the length of time (in seconds) specified in the delay statement. For example:
Listing 7: show_delay.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Delay is
4
5 task T;
6
7 task body T is
8 begin
9 for I in 1 .. 5 loop
10 Put_Line ("hello from task T");
11 delay 1.0;
12 -- ^ Wait 1.0 seconds
13 end loop;
14 end T;
15 begin
16 delay 1.5;
17 Put_Line ("hello from main");
18 end Show_Delay;
Project: Courses.Intro_To_Ada.Tasking.Show_Delay
MD5: 4a6e8039744301a128e8fb2dd27902a5
Runtime output
In this example, we're making the task T wait one second after each time it displays the
"hello" message. In addition, the main task is waiting 1.5 seconds before displaying its own
"hello" message
Listing 8: show_rendezvous.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Rendezvous is
4
5 task T is
6 entry Start;
7 end T;
8
9 task body T is
10 begin
11 accept Start;
12 -- ^ Waiting for somebody
13 -- to call the entry
14
18 begin
19 Put_Line ("In Main");
20
Project: Courses.Intro_To_Ada.Tasking.Show_Rendezvous
MD5: 479eea7adc876ac359ad20ac6e3acf66
Runtime output
In Main
In T
In this example, we declare an entry Start for task T. In the task body, we implement this
entry using accept Start. When task T reaches this point, it waits for some other task to
call its entry. This synchronization occurs in the T.Start statement. After the rendezvous
completes, the main task and task T again run concurrently until they synchronize one final
time when the main subprogram Show_Rendezvous finishes.
An entry may be used to perform more than a simple task synchronization: it also may
perform multiple statements during the time both tasks are synchronized. We do this with
a do ... end block. For the previous example, we would simply write accept Start do
<statements>; end;. We use this kind of block in the next example.
Listing 9: show_rendezvous_loop.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
5 task T is
6 entry Reset;
7 entry Increment;
8 end T;
9
10 task body T is
11 Cnt : Integer := 0;
12 begin
13 loop
14 select
15 accept Reset do
16 Cnt := 0;
17 end Reset;
18 Put_Line ("Reset");
19 or
20 accept Increment do
21 Cnt := Cnt + 1;
22 end Increment;
23 Put_Line ("In T's loop ("
24 & Integer'Image (Cnt)
25 & ")");
26 or
27 terminate;
28 end select;
29 end loop;
30 end T;
31
32 begin
33 Put_Line ("In Main");
34
35 for I in 1 .. 4 loop
36 -- Calling T's entry multiple times
37 T.Increment;
38 end loop;
39
40 T.Reset;
41 for I in 1 .. 4 loop
42 -- Calling T's entry multiple times
43 T.Increment;
44 end loop;
45
46 end Show_Rendezvous_Loop;
Project: Courses.Intro_To_Ada.Tasking.Show_Rendezvous_Loop
MD5: 0542dbc029cffb9f794d761bab9f3a9d
Runtime output
In Main
In T's loop ( 1)
In T's loop ( 2)
In T's loop ( 3)
In T's loop ( 4)
Reset
In T's loop ( 1)
In T's loop ( 2)
In T's loop ( 3)
(continues on next page)
In this example, the task body implements an infinite loop that accepts calls to the Reset
and Increment entry. We make the following observations:
• The accept E do ... end block is used to increment a counter.
– As long as task T is performing the do ... end block, the main task waits for the
block to complete.
• The main task is calling the Increment entry multiple times in the loop from 1 .. 4.
It is also calling the Reset entry before the second loop.
– Because task T contains an infinite loop, it always accepts calls to the Reset and
Increment entries.
– When the master construct of the subtask (the Show_Rendezvous_Loop subpro-
gram) completes, it checks the status of the T task. Even though task T could
accept new calls to the Reset or Increment entries, the master construct is al-
lowed to terminate task T due to the or terminate part of the select statement.
In this case, we can't guarantee that exactly 10 seconds have elapsed after 10 calls
to the delay statement because a time drift may be introduced by the Computa-
tional_Intensive_App procedure. In many cases, this time drift is not relevant, so using
the delay keyword is good enough.
However, there are situations where a time drift isn't acceptable. In those cases, we need
to use the delay until statement, which accepts a precise time for the end of the delay,
allowing us to define a regular interval. This is useful, for example, in real-time applications.
We will soon see an example of how this time drift may be introduced and how the delay
until statement circumvents the problem. But before we do that, we look at a package
containing a procedure allowing us to measure the elapsed time (Show_Elapsed_Time) and
a dummy Computational_Intensive_App procedure which is simulated by using a simple
delay. This is the complete package:
3 package Delay_Aux_Pkg is
4
8 procedure Show_Elapsed_Time
9 with Inline;
10
18 end Delay_Aux_Pkg;
5 procedure Show_Elapsed_Time is
6 Now_Time : Time;
7 Elapsed_Time : Time_Span;
8 begin
9 Now_Time := Clock;
10 Elapsed_Time := Now_Time - Start_Time;
11 Put_Line ("Elapsed time "
12 & Duration'Image
13 (To_Duration (Elapsed_Time))
14 & " seconds");
15 end Show_Elapsed_Time;
16
17 procedure Computational_Intensive_App is
18 begin
19 delay 0.5;
20 end Computational_Intensive_App;
21
22 end Delay_Aux_Pkg;
Using this auxiliary package, we're now ready to write our time-drifting application:
4 with Delay_Aux_Pkg;
5
6 procedure Show_Time_Task is
7 package Aux renames Delay_Aux_Pkg;
8
9 task T;
10
11 task body T is
12 Cnt : Integer := 1;
13 begin
14 for I in 1 .. 5 loop
15 delay 1.0;
16
17 Aux.Show_Elapsed_Time;
18 Aux.Computational_Intensive_App;
(continues on next page)
27 begin
28 null;
29 end Show_Time_Task;
Runtime output
Elapsed time 1.000777218 seconds
Cycle # 1
Elapsed time 2.509336022 seconds
Cycle # 2
Elapsed time 4.022739679 seconds
Cycle # 3
Elapsed time 5.526925331 seconds
Cycle # 4
Elapsed time 7.037777616 seconds
Cycle # 5
Finished time-drifting loop
We can see by running the application that we already have a time difference of about
four seconds after three iterations of the loop due to the drift introduced by Computa-
tional_Intensive_App. Using the delay until statement, however, we're able to avoid
this time drift and have a regular interval of exactly one second:
4 with Delay_Aux_Pkg;
5
6 procedure Show_Time_Task is
7 package Aux renames Delay_Aux_Pkg;
8
9 task T;
10
11 task body T is
12 Cycle : constant Time_Span :=
13 Milliseconds (1000);
14 Next : Time := Aux.Get_Start_Time
15 + Cycle;
16
17 Cnt : Integer := 1;
18 begin
19 for I in 1 .. 5 loop
20 delay until Next;
21
22 Aux.Show_Elapsed_Time;
23 Aux.Computational_Intensive_App;
(continues on next page)
36 begin
37 null;
38 end Show_Time_Task;
Project: Courses.Intro_To_Ada.Tasking.Show_Time
MD5: 1456c0feee6def8b370d994c0ab75a15
Runtime output
Now, as we can see by running the application, the delay until statement ensures that the
Computational_Intensive_App doesn't disturb the regular interval of one second between
iterations.
® Important
Objects can be protected from concurrent access using Ada tasks. In fact, this was the
only way of protecting objects from concurrent access in Ada 83 (the first version of
the Ada language). However, the use of protected objects is much simpler than using
similar mechanisms implemented using only tasks. Therefore, you should use protected
objects when your main goal is only to protect data.
3 procedure Show_Protected_Objects is
4
5 protected Obj is
6 -- Operations go here (only subprograms)
7 procedure Set (V : Integer);
8 function Get return Integer;
9 private
10 -- Data goes here
11 Local : Integer := 0;
12 end Obj;
13
28 begin
29 Obj.Set (5);
30 Put_Line ("Number is: "
31 & Integer'Image (Obj.Get));
32 end Show_Protected_Objects;
Project: Courses.Intro_To_Ada.Tasking.Show_Protected_Objects
MD5: dd97dd584ba2f13def3c04725d4e48a7
Runtime output
Number is: 5
In this example, we define two operations for Obj: Set and Get. The implementation of
these operations is in the Obj body. The syntax used for writing these operations is the same
as that for normal procedures and functions. The implementation of protected objects is
straightforward — we simply access and update Local in these subprograms. To call these
operations in the main application, we use prefixed notation, e.g., Obj.Get.
15.2.2 Entries
In addition to protected procedures and functions, you can also define protected entry
points. Do this using the entry keyword. Protected entry points allow you to define barri-
ers using the when keyword. Barriers are conditions that must be fulfilled before the entry
can start performing its actual processing — we speak of releasing the barrier when the
condition is fulfilled.
The previous example used procedures and functions to define operations on the protected
objects. However, doing so permits reading protected information (via Obj.Get) before it's
set (via Obj.Set). To allow that to be a defined operation, we specified a default value (0).
Instead, by rewriting Obj.Get using an entry instead of a function, we implement a barrier,
ensuring no task can read the information before it's been set.
The following example implements the barrier for the Obj.Get operation. It also contains
two concurrent subprograms (main task and task T) that try to access the protected object.
3 procedure Show_Protected_Objects_Entries is
4
5 protected Obj is
6 procedure Set (V : Integer);
7 entry Get (V : out Integer);
8 private
9 Local : Integer;
10 Is_Set : Boolean := False;
11 end Obj;
12
34 N : Integer := 0;
35
36 task T;
37
38 task body T is
39 begin
40 Put_Line
41 ("Task T will delay for 4 seconds...");
42 delay 4.0;
43
44 Put_Line
(continues on next page)
48 Put_Line
49 ("Task T has just set Obj...");
50 end T;
51 begin
52 Put_Line
53 ("Main application will get Obj...");
54 Obj.Get (N);
55
56 Put_Line
57 ("Main application has retrieved Obj...");
58 Put_Line
59 ("Number is: " & Integer'Image (N));
60
61 end Show_Protected_Objects_Entries;
Project: Courses.Intro_To_Ada.Tasking.Show_Protected_Objects_Entries
MD5: c1134445a96700b871fb76c4d6342359
Runtime output
As we see by running it, the main application waits until the protected object is set (by the
call to Obj.Set in task T) before it reads the information (via Obj.Get). Because a 4-second
delay has been added in task T, the main application is also delayed by 4 seconds. Only
after this delay does task T set the object and release the barrier in Obj.Get so that the
main application can then resume processing (after the information is retrieved from the
protected object).
3 procedure Show_Simple_Task is
4 task T;
5
6 task body T is
7 begin
8 Put_Line ("In task T");
9 end T;
10 begin
11 Put_Line ("In main");
12 end Show_Simple_Task;
Project: Courses.Intro_To_Ada.Tasking.Show_Simple_Task
MD5: b17d9b35b4b2b53bc59776749e1be219
Runtime output
In task T
In main
We now rewrite it by replacing task T with task type TT. We declare a task (A_Task)
based on the task type TT after its definition:
3 procedure Show_Simple_Task_Type is
4 task type TT;
5
6 task body TT is
7 begin
8 Put_Line ("In task type TT");
9 end TT;
10
11 A_Task : TT;
12 begin
13 Put_Line ("In main");
14 end Show_Simple_Task_Type;
Project: Courses.Intro_To_Ada.Tasking.Show_Simple_Task_Type
MD5: 24c26dcbba6f5c54f0a7d47c3c0da728
Runtime output
In task type TT
In main
We can extend this example and create an array of tasks. Since we're using the same
syntax as for variable declarations, we use a similar syntax for task types: array (<>) of
Task_Type. Also, we can pass information to the individual tasks by defining a Start entry.
Here's the updated example:
3 procedure Show_Task_Type_Array is
4 task type TT is
5 entry Start (N : Integer);
6 end TT;
7
8 task body TT is
9 Task_N : Integer;
10 begin
11 accept Start (N : Integer) do
12 Task_N := N;
13 end Start;
14 Put_Line ("In task T: "
15 & Integer'Image (Task_N));
16 end TT;
17
Runtime output
In main
In task T: 1
In task T: 2
In task T: 3
In task T: 4
In task T: 5
In this example, we're declaring five tasks in the array My_Tasks. We pass the array index
to the individual tasks in the entry point (Start). After the synchronization between the
individual subtasks and the main task, each subtask calls Put_Line concurrently.
3 procedure Show_Protected_Object_Type is
(continues on next page)
24 Obj : P_Obj_Type;
25 begin
26 Obj.Set (5);
27 Put_Line ("Number is: "
28 & Integer'Image (Obj.Get));
29 end Show_Protected_Object_Type;
Project: Courses.Intro_To_Ada.Tasking.Show_Protected_Object_Type
MD5: c50321e55afef0d72f263fee0669e55f
Runtime output
Number is: 5
In this example, instead of directly defining the protected object Obj, we first define a
protected type P_Obj_Type and then declare Obj as an object of that protected type. Note
that the main application hasn't changed: we still use Obj.Set and Obj.Get to access the
protected object, just like in the original example.
SIXTEEN
DESIGN BY CONTRACTS
Listing 1: show_simple_precondition.adb
1 procedure Show_Simple_Precondition is
2
161
Introduction to Ada
Project: Courses.Intro_To_Ada.Contracts.Show_Simple_Precondition
MD5: 87b6e080555603111801a0fcd2469acd
Runtime output
In this example, we want to prevent the name field in our database from containing an
empty string. We implement this requirement by using a precondition requiring that the
length of the string used for the Name parameter of the DB_Entry procedure is greater than
zero. If the DB_Entry procedure is called with an empty string for the Name parameter, the
call will fail because the precondition is not met.
GNAT handles pre- and postconditions by generating runtime assertions for them. By
default, however, assertions aren't enabled. Therefore, in order to check pre- and post-
conditions at runtime, you need to enable assertions by using the -gnata switch.
Before we get to our next example, let's briefly discuss quantified expressions, which are
quite useful in concisely writing pre- and postconditions. Quantified expressions return a
Boolean value indicating whether elements of an array or container match the expected
condition. They have the form: (for all I in A'Range => <condition on A(I)>, where
A is an array and I is an index. Quantified expressions using for all check whether the
condition is true for every element. For example:
(for all I in A'Range => A (I) = 0)
This quantified expression is only true when all elements of the array A have a value of zero.
Another kind of quantified expressions uses for some. The form looks similar: (for some
I in A'Range => <condition on A(I)>. However, in this case the qualified expression
tests whether the condition is true only on some elements (hence the name) instead of all
elements.
We illustrate postconditions using the following example:
Listing 2: show_simple_postcondition.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Simple_Postcondition is
4
7 type Int_8_Array is
8 array (Integer range <>) of Int_8;
9
35 Square (V);
36 for E of V loop
37 Put_Line ("Square: "
38 & Int_8'Image (E));
39 end loop;
40 end Show_Simple_Postcondition;
Project: Courses.Intro_To_Ada.Contracts.Show_Simple_Postcondition
MD5: b9bae9fe09cefcbe6769ad9cd6739e2a
Runtime output
Original: -2
Original: -1
Original: 0
Original: 1
Original: 10
Original: 11
Square: 4
Square: 1
Square: 0
Square: 1
Square: 100
Square: 121
We declare a signed 8-bit type Int_8 and an array of that type (Int_8_Array). We want to
ensure each element of the array is squared after calling the procedure Square for an object
of the Int_8_Array type. We do this with a postcondition using a for all expression. This
postcondition also uses the 'Old attribute to refer to the original value of the parameter
(before the call).
We also want to ensure that the result of calls to the Square function for the Int_8 type are
greater than the input to that call. To do that, we write a postcondition using the 'Result
attribute of the function and comparing it to the input value.
We can use both pre- and postconditions in the declaration of a single subprogram. For
example:
Listing 3: show_simple_contract.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Simple_Contract is
(continues on next page)
18 V : Int_8;
19 begin
20 V := Square (11);
21 Put_Line ("Square of 11 is "
22 & Int_8'Image (V));
23
Runtime output
Square of 11 is 121
In this example, we want to ensure that the input value of calls to the Square function for
the Int_8 type won't cause overflow in that function. We do this by converting the input
value to the Integer type, which is used for the temporary calculation, and check if the
result is in the appropriate range for the Int_8 type. We have the same postcondition in
this example as in the previous one.
16.2 Predicates
Predicates specify expectations regarding types. They're similar to pre- and postconditions,
but apply to types instead of subprograms. Their conditions are checked for each object of a
given type, which allows verifying that an object of type T is conformant to the requirements
of its type.
There are two kinds of predicates: static and dynamic. In simple terms, static predicates
are used to check objects at compile-time, while dynamic predicates are used for checks
at run time. Normally, static predicates are used for scalar types and dynamic predicates
for the more complex types.
Static and dynamic predicates are specified using the following clauses, respectively:
• with Static_Predicate => <property>
• with Dynamic_Predicate => <property>
Listing 4: show_dynamic_predicate_courses.adb
1 with Ada.Calendar; use Ada.Calendar;
2
3 with Ada.Containers.Vectors;
4
5 with Ada.Strings.Unbounded;
6 use Ada.Strings.Unbounded;
7
8 procedure Show_Dynamic_Predicate_Courses is
9
10 package Courses is
11 type Course_Container is private;
12
42 use Courses;
43
44 CC : Course_Container;
45 begin
46 Add (CC,
47 Course'(
48 Name =>
49 To_Unbounded_String
50 ("Intro to Photography"),
51 Start_Date =>
52 Time_Of (2018, 5, 1),
53 End_Date =>
54 Time_Of (2018, 5, 10)));
55
68 end Show_Dynamic_Predicate_Courses;
Project: Courses.Intro_To_Ada.Contracts.Show_Dynamic_Predicate_Courses
MD5: 8bd6539e72995fececfcdf9666ffd04f
Runtime output
In this example, the package Courses defines a type Course and a type Course_Container,
an object of which contains all courses. We want to ensure that the dates of each course
are consistent, specifically that the start date is no later than the end date. To enforce this
rule, we declare a dynamic predicate for the Course type that performs the check for each
object. The predicate uses the type name where a variable of that type would normally be
used: this is a reference to the instance of the object being tested.
Note that the example above makes use of unbounded strings and dates. Both types are
available in Ada's standard library. Please refer to the following sections for more informa-
tion about:
• the unbounded string type (Unbounded_String): Unbounded Strings (page 243) sec-
tion;
• dates and times: Dates & Times (page 227) section.
Static predicates, as mentioned above, are mostly used for scalar types and checked during
compilation. They're particularly useful for representing non-contiguous elements of an
enumeration. A classic example is a list of week days:
We can easily create a sub-list of work days in the week by specifying a subtype with a
range based on Week. For example:
Ranges in Ada can only be specified as contiguous lists: they don't allow us to pick specific
days. However, we may want to create a list containing just the first, middle and last day
of the work week. To do that, we use a static predicate:
Listing 5: show_predicates.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Predicates is
4
23 Num_Tests : Tests_Week :=
24 (Mon => 3, Tue => 0,
25 Wed => 4, Thu => 0,
26 Fri => 2, Sat => 0,
27 Sun => 0);
28
39 begin
40 Display_Tests (Num_Tests);
41
Project: Courses.Intro_To_Ada.Contracts.Show_Predicates
MD5: 126c47033fc67fc8b6d7f6479205e752
Runtime output
Here we have an application that wants to perform tests only on three days of the work
week. These days are specified in the Test_Days subtype. We want to track the number
of tests that occur each day. We declare the type Tests_Week as an array, an object of
which will contain the number of tests done each day. According to our requirements,
these tests should happen only in the aforementioned three days; on other days, no tests
should be performed. This requirement is implemented with a dynamic predicate of the
type Tests_Week. Finally, the actual information about these tests is stored in the array
Num_Tests, which is an instance of the Tests_Week type.
The dynamic predicate of the Tests_Week type is verified during the initialization of
Num_Tests. If we have a non-conformant value there, the check will fail. However, as
we can see in our example, individual assignments to elements of the array do not trigger
a check. We can't check for consistency at this point because the initialization of the a
complex data structure (such as arrays or records) may not be performed with a single as-
signment. However, as soon as the object is passed as an argument to a subprogram, the
dynamic predicate is checked because the subprogram requires the object to be consistent.
This happens in the last call to Display_Tests in our example. Here, the predicate check
fails because the previous assignment has a non-conformant value.
We could rewrite our previous example and replace dynamic predicates by type invariants.
It would look like this:
Listing 6: show_type_invariant.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Calendar; use Ada.Calendar;
3
4 with Ada.Containers.Vectors;
5
6 with Ada.Strings.Unbounded;
7 use Ada.Strings.Unbounded;
8
9 procedure Show_Type_Invariant is
10
11 package Courses is
12 type Course is private
13 with Type_Invariant => Check (Course);
14
20 function Init
21 (Name : String;
22 Start_Date, End_Date : Time)
23 return Course;
24
28 private
29 type Course is record
30 Name : Unbounded_String;
31 Start_Date : Time;
32 End_Date : Time;
33 end record;
34
56 function Init
57 (Name : String;
58 Start_Date, End_Date : Time)
59 return Course is
60 begin
61 return
(continues on next page)
69 use Courses;
70
71 CC : Course_Container;
72 begin
73 Add (CC,
74 Init (Name =>
75 "Intro to Photography",
76 Start_Date =>
77 Time_Of (2018, 5, 1),
78 End_Date =>
79 Time_Of (2018, 5, 10)));
80
Project: Courses.Intro_To_Ada.Contracts.Show_Type_Invariant
MD5: c6ef863da94285f927dd106645af8650
Runtime output
The major difference is that the Course type was a visible (public) type of the Courses
package in the previous example, but in this example is a private type.
SEVENTEEN
INTERFACING WITH C
Ada allows us to interface with code in many languages, including C and C++. This section
discusses how to interface with C.
project Multilang is
end Multilang;
Listing 1: show_c_enum.adb
1 procedure Show_C_Enum is
2
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Enum
MD5: a14d7d981fd7d6d806cf3c55f35e19c8
To interface with C's built-in types, we use the Interfaces.C package, which contains most
of the type definitions we need. For example:
171
Introduction to Ada
Listing 2: show_c_struct.adb
1 with Interfaces.C; use Interfaces.C;
2
3 procedure Show_C_Struct is
4
13 begin
14 null;
15 end Show_C_Struct;
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Struct
MD5: dda4d3f8e4ddf5c5138a990a9a8ac427
Here, we're interfacing with a C struct (C_Struct) and using the corresponding data types
in C (int, long, unsigned and double). This is the declaration in C:
Listing 3: c_struct.h
1 struct c_struct
2 {
3 int a;
4 long b;
5 unsigned c;
6 double d;
7 };
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Struct
MD5: 58709b6a9eea2606d7ec0aaca0a749ff
Listing 4: my_func.h
1 int my_func (int a);
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Func
MD5: 37b9d7ba668f7ec83c2b27ee33637937
Listing 5: my_func.c
1 #include "my_func.h"
2
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Func
MD5: 284b1639cb393fc14ed196d78429f3ba
We can interface this code in Ada using the Import aspect. For example:
Listing 6: show_c_func.adb
1 with Interfaces.C; use Interfaces.C;
2 with Ada.Text_IO; use Ada.Text_IO;
3
4 procedure Show_C_Func is
5
14 V : int;
15 begin
16 V := my_func (2);
17 Put_Line ("Result is " & int'Image (V));
18 end Show_C_Func;
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Func
MD5: 6c5d85c1debdeaa642946eacf413dfd2
If you want, you can use a different subprogram name in the Ada code. For example, we
could call the C function Get_Value:
Listing 7: show_c_func.adb
1 with Interfaces.C; use Interfaces.C;
2 with Ada.Text_IO; use Ada.Text_IO;
3
4 procedure Show_C_Func is
5
Listing 8: c_api.ads
1 with Interfaces.C; use Interfaces.C;
2
3 package C_API is
4
11 end C_API;
Listing 9: c_api.adb
1 package body C_API is
2
8 end C_API;
On the C side, we do the same as we would if the function were written in C: simply declare
it using the extern keyword. For example:
7 int v = my_func(2);
8
11 return 0;
12 }
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Ada_Func
MD5: 69301036be9be16ed45895c2a86bc352
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Vars
MD5: 11ba8f7a72ce7058571994870a02b052
3 int func_cnt = 0;
4
9 return a * 2;
10 }
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Vars
MD5: 23631537cb877a03d1243c94cb7b48e8
4 procedure Show_C_Func is
5
11 V : int;
12
13 func_cnt : int
14 with
15 Import => True,
16 Convention => C;
17 -- We can access the func_cnt variable
18 -- from test.c
19
20 begin
21 V := my_func (1);
22 V := my_func (2);
23 V := my_func (3);
24
Project: Courses.Intro_To_Ada.Interfacing_With_C.Ada_C_Vars
MD5: cf64a9dfbc6be853ba19729fe55f0ba4
As we see by running the application, the value of the counter is the number of times
my_func was called.
We can use the External_Name aspect to give a different name for the variable in the Ada
application in the same way we do for subprograms.
3 package C_API is
4
5 func_cnt : int := 0
6 with
(continues on next page)
16 end C_API;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Ada_Vars
MD5: fc118cddd797b669d2c68e57f90f69b2
9 end C_API;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Ada_Vars
MD5: adff5f3088da8b0dd853f1fb8b1e204f
In the C application, we just need to declare the variable and use it:
9 int v;
10
11 v = my_func(1);
12 v = my_func(2);
13 v = my_func(3);
14
20 return 0;
21 }
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Ada_Vars
MD5: 07fb3fbadb8ed4c0543fbfd7b5ef5c57
Again, by running the application, we see that the value from the counter is the number of
times that my_func was called.
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds
MD5: 11ba8f7a72ce7058571994870a02b052
6 package test_h is
7
14 end test_h;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds
MD5: 8d18aeae72dba3a9ab4f9f3943fab839
5 procedure Show_C_Func is
6 V : int;
7 begin
8 V := my_func (1);
9 V := my_func (2);
10 V := my_func (3);
11
You can specify the name of the parent unit for the bindings you're creating as the operand
to fdump-ada-spec:
gcc -c -fdump-ada-spec -fada-spec-parent=Ext_C_Code -C ./test.h
5 end Ext_C_Code.test_h;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds_3
MD5: af642d9ea995bf01f13f8ff41bb0f4f6
5 #include "test.h"
6
7 struct test {
8 char name[80];
9 char address[120];
10 };
11
12 static size_t
13 strlcpy_stat(char *dst,
14 const char *src,
15 size_t dstsize)
16 {
17 size_t len = strlen(src);
18 if (dstsize) {
19 size_t bl = (len < dstsize-1 ?
20 len : dstsize-1);
21 ((char*)memcpy(dst, src, bl))[bl] = 0;
22 }
23 return len;
24 }
25
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds_3
MD5: 32652eb76ad92212609680d64e5687d3
8 package test_h is
9
30 end test_h;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds_3
MD5: 3bf8f01b94fd28594e4121a6a36afdf7
As we can see, the binding generator completely ignores the declaration struct test and
all references to the test struct are replaced by addresses (System.Address). Neverthe-
less, these bindings are good enough to allow us to create a test application in Ada:
3 with Interfaces.C;
4 use Interfaces.C;
5
6 with Interfaces.C.Strings;
7 use Interfaces.C.Strings;
8
11 with System;
12
13 procedure Show_Automatic_C_Struct_Bindings is
14
20 T : System.Address := test_create;
21
22 begin
23 test_reset (T);
24 test_set_name (T, Name);
25 test_set_address (T, Address);
26
27 test_display (T);
28 test_destroy (T);
29 end Show_Automatic_C_Struct_Bindings;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds_3
MD5: 99d64fb14d9c869d140dd2fb7d3888d7
We can successfully bind our C code with Ada using the automatically-generated bindings,
but they aren't ideal. Instead, we would prefer Ada bindings that match our (human) inter-
pretation of the C header file. This requires manual analysis of the header file. The good
news is that we can use the automatic generated bindings as a starting point and adapt
them to our needs. For example, we can:
1. Define a Test type based on System.Address and use it in all relevant functions.
2. Remove the test_ prefix in all operations on the Test type.
This is the resulting specification:
6 package adapted_test_h is
7
30 end adapted_test_h;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds_3
MD5: 5cc875e1b01af839141e5e623f6c5b7a
4 with Interfaces.C.Strings;
5 use Interfaces.C.Strings;
6
9 with System;
10
11 procedure Show_Adapted_C_Struct_Bindings is
12
18 T : Test := Create;
19
20 begin
21 Reset (T);
22 Set_Name (T, Name);
23 Set_Address (T, Address);
24
25 Display (T);
26 Destroy (T);
27 end Show_Adapted_C_Struct_Bindings;
Project: Courses.Intro_To_Ada.Interfacing_With_C.C_Binds_3
MD5: 626d07b080fbbd2bf1d5f9140b64955c
Now we can use the Test type and its operations in a clean, readable way.
EIGHTEEN
OBJECT-ORIENTED PROGRAMMING
185
Introduction to Ada
® Note
It's possible to program in Ada without ever creating tagged types. If that's your prefered
style of programming or you have no specific use for tagged types, feel free to not use
them, as is the case for many features of Ada.
However, they can be the best way to express solutions to certain problems and they
may be the best way to solve your problem. If that's the case, read on!
Listing 1: newtypes.ads
1 package Newtypes is
2 type Point is record
3 X, Y : Integer;
4 end record;
5
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Newtypes
MD5: 0d45096755b4bfb08ba8db19ecba3f57
Type derivation is useful to enforce strong typing because the type system treats the two
types as incompatible.
But the benefits are not limited to that: you can inherit things from the type you derive
from. You not only inherit the representation of the data, but you can also inherit behavior.
When you inherit a type you also inherit what are called primitive operations. A primitive
operation (or just a primitive) is a subprogram attached to a type. Ada defines primitives
as subprograms defined in the same scope as the type.
Á Attention
Listing 2: primitives.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Primitives is
4 package Week is
5 type Days is (Monday, Tuesday, Wednesday,
6 Thursday, Friday,
(continues on next page)
9 -- Print_Day is a primitive
10 -- of the type Days
11 procedure Print_Day (D : Days);
12 end Week;
13
21 use Week;
22 type Weekend_Days is new
23 Days range Saturday .. Sunday;
24
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Primitives
MD5: eb1b0eb66f03a4a17bd9686ec4e12e2e
Runtime output
SATURDAY
This kind of inheritance can be very useful, and is not limited to record types (you can use
it on discrete types, as in the example above), but it's only superficially similar to object-
oriented inheritance:
• Records can't be extended using this mechanism alone. You also can't specify a new
representation for the new type: it will always have the same representation as the
base type.
• There's no facility for dynamic dispatch or polymorphism. Objects are of a fixed, static
type.
There are other differences, but it's not useful to list them all here. Just remember that this
is a kind of inheritance you can use if you only want to statically inherit behavior without
duplicating code or using composition, but a kind you can't use if you want any dynamic
features that are usually associated with OOP.
Tagged types are very similar to normal records except that some functionality is added:
• Types have a tag, stored inside each object, that identifies the runtime type20 of that
object.
• Primitives can dispatch. A primitive on a tagged type is what you would call a method
in Java or C++. If you derive a base type and override a primitive of it, you can often
call it on an object with the result that which primitive is called depends on the exact
runtime type of the object.
• Subtyping rules are introduced allowing a tagged type derived from a base type to be
statically compatible with the base type.
Let's see our first tagged type declarations:
Listing 3: p.ads
1 package P is
2 type My_Class is tagged null record;
3 -- Just like a regular record, but
4 -- with tagged qualifier
5
21 overriding
22 procedure Foo (Self : in out Derived);
23 -- The "overriding" qualifier is optional,
24 -- but if it is present, it must be valid.
25 end P;
Listing 4: p.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 package body P is
4 procedure Foo (Self : in out My_Class) is
5 begin
6 Put_Line ("In My_Class.Foo");
7 end Foo;
8
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Tagged_Types
MD5: 45baaad66a1047358addb574d0fa00bc
Listing 5: main.adb
1 with P; use P;
2
3 procedure Main is
4
5 O1 : My_Class;
6 -- Declaring an object of type My_Class
7
11 O3 : My_Class := O2;
12 -- INVALID: Trying to assign a value
13 -- of type derived to a variable of
14 -- type My_Class.
15 begin
16 null;
17 end Main;
Build output
main.adb:11:21: error: expected type "My_Class" defined at p.ads:2
main.adb:11:21: error: found type "Derived" defined at p.ads:16
gprbuild: *** compilation phase failed
This is because an object of a type T is exactly of the type T, whether T is tagged or not.
What you want to say as a programmer is "I want O3 to be able to hold an object of type
My_Class or any type descending from My_Class". Here's how you do that:
Listing 6: main.adb
1 with P; use P;
2
3 procedure Main is
4 O1 : My_Class;
5 -- Declare an object of type My_Class
6
10 O3 : My_Class'Class := O2;
11 -- Now valid: My_Class'Class designates
12 -- the classwide type for My_Class,
(continues on next page)
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Tagged_Types
MD5: 35412176a248015a26e507164ce526af
Á Attention
Because an object of a classwide type can be the size of any descendant of its base type,
it has an unknown size. It's therefore an indefinite type, with the expected restrictions:
• It can't be stored as a field/component of a record
• An object of a classwide type needs to be initialized immediately (you can't specify
the constraints of such a type in any way other than by initializing it).
Listing 7: main.adb
1 with P; use P;
2
3 procedure Main is
4 O1 : My_Class;
5 -- Declare an object of type My_Class
6
10 O3 : My_Class'Class := O2;
11
12 O4 : My_Class'Class := O1;
13 begin
14 Foo (O1);
15 -- Non dispatching: Calls My_Class.Foo
16 Foo (O2);
17 -- Non dispatching: Calls Derived.Foo
18 Foo (O3);
19 -- Dispatching: Calls Derived.Foo
20 Foo (O4);
(continues on next page)
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Tagged_Types
MD5: 7631f823b0dd9e5474f6bb2dc35af2a2
Runtime output
In My_Class.Foo
In Derived.Foo, A = 12
In Derived.Foo, A = 12
In My_Class.Foo
® Attention
You can convert an object of type Derived to an object of type My_Class. This is called
a view conversion in Ada parlance and is useful, for example, if you want to call a parent
method.
In that case, the object really is converted to a My_Class object, which means its tag is
changed. Since tagged objects are always passed by reference, you can use this kind of
conversion to modify the state of an object: changes to converted object will affect the
original one.
Listing 8: main.adb
1 with P; use P;
2
3 procedure Main is
4 O1 : Derived := (A => 12);
5 -- Declare an object of type Derived
6
9 O3 : My_Class'Class := O2;
10 begin
11 Foo (O1);
12 -- Non dispatching: Calls Derived.Foo
13 Foo (O2);
14 -- Non dispatching: Calls My_Class.Foo
15
16 Foo (O3);
17 -- Dispatching: Calls My_Class.Foo
18 end Main;
Runtime output
In Derived.Foo, A = 12
In My_Class.Foo
In My_Class.Foo
Listing 9: main.adb
1 with P; use P;
2
3 procedure Main is
4 O1 : My_Class;
5 -- Declare an object of type My_Class
6
10 O3 : My_Class'Class := O2;
11
12 O4 : My_Class'Class := O1;
13 begin
14 O1.Foo;
15 -- Non dispatching: Calls My_Class.Foo
16 O2.Foo;
17 -- Non dispatching: Calls Derived.Foo
18 O3.Foo;
19 -- Dispatching: Calls Derived.Foo
20 O4.Foo;
21 -- Dispatching: Calls My_Class.Foo
22 end Main;
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Tagged_Types
MD5: 9c6ebdfec9ceeb986d92eb90ec9ff59b
Runtime output
In My_Class.Foo
In Derived.Foo, A = 12
In Derived.Foo, A = 12
In My_Class.Foo
If the dispatching parameter of a primitive is the first parameter, which is the case in our
examples, you can call the primitive using the dot notation. Any remaining parameter are
passed normally:
3 procedure Main is
4 package Extend is
5 type D2 is new Derived with null record;
6
19 use Extend;
20
Runtime output
In Derived.Foo, A = 17
Naturally, you can combine both limited and private types and declare a tagged limited
private type:
8 end P;
3 procedure Main is
4 T1, T2 : T;
5 begin
6 T1.Init;
7 T2.Init;
8
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Tagged_Limited_Private_
↪Types
MD5: 68240374505bcaf7aad4ebaed3b9127b
Note that the code in the Main procedure above presents two assignments that trigger
compilation errors because type T is limited private. In fact, you cannot:
• assign to T1.E directly because type T is private;
• assign T1 to T2 because type T is limited.
In this case, there's no distinction between tagged and non-tagged types: these compilation
errors would also occur for non-tagged types.
3 package body P is
4
17 end P;
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Classwide_Error
MD5: fd5cb99925d3c88536546aa0be8104b7
Note that we're using null records for both types T and T_New. Although these types don't
actually have any component, we can still use them to demonstrate dispatching. Also note
that the example above makes use of the 'External_Tag attribute in the implementation
of the Show procedure to get a string for the corresponding tagged type.
As we've seen before, we must use a classwide type to create objects that can make dis-
patching calls. In other words, objects of type T'Class will dispatch. For example:
3 procedure Dispatching_Example is
4 T2 : T_New;
5 T_Dispatch : constant T'Class := T2;
6 begin
7 T_Dispatch.Show;
8 end Dispatching_Example;
Runtime output
Using type P.T_NEW
A more useful application is to declare an array of objects that can dispatch. For example,
we'd like to declare an array T_Arr, loop over this array and dispatch according to the actual
type of each individual element:
for I in T_Arr'Range loop
T_Arr (I).Show;
-- Call Show procedure according
-- to actual type of T_Arr (I)
end loop;
3 procedure Classwide_Compilation_Error is
4 T_Arr : array (1 .. 2) of T'Class;
5 -- ^
6 -- Compilation Error!
7 begin
8 for I in T_Arr'Range loop
9 T_Arr (I).Show;
10 end loop;
11 end Classwide_Compilation_Error;
Build output
classwide_compilation_error.adb:4:32: error: unconstrained element type in array␣
↪declaration
In fact, it's impossible for the compiler to know which type would actually be used for each
element of the array. However, if we use dynamic allocation via access types, we can
allocate objects of different types for the individual elements of an array T_Arr. We do this
by using classwide access types, which have the following format:
type T_Class is access T'Class;
We can rewrite the previous example using the T_Class type. In this case, dynamically
allocated objects of this type will dispatch according to the actual type used during the
allocation. Also, let's introduce an Init procedure that won't be overridden for the derived
T_New type. This is the adapted code:
16 end P;
3 package body P is
4
23 end P;
4 procedure Main is
5 T_Arr : array (1 .. 2) of T_Class;
6 begin
7 T_Arr (1) := new T;
8 T_Arr (2) := new T_New;
9
14 T_Arr (I).Init;
15 T_Arr (I).Show;
16
17 Put_Line ("-----------");
(continues on next page)
Project: Courses.Intro_To_Ada.Object_Oriented_Programming.Classwide_Access
MD5: 97c05a8f911d0a0e39c0cc90fae184a7
Runtime output
Element # 1
Initializing type T...
Using type P.T
-----------
Element # 2
Initializing type T...
Using type P.T_NEW
-----------
In this example, the first element (T_Arr (1)) is of type T, while the second element is
of type T_New. When running the example, the Init procedure of type T is called for both
elements of the T_Arr array, while the call to the Show procedure selects the corresponding
procedure according to the type of each element of T_Arr.
NINETEEN
In previous chapters, we've used arrays as the standard way to group multiple objects of a
specific data type. In many cases, arrays are good enough for manipulating those objects.
However, there are situations that require more flexibility and more advanced operations.
For those cases, Ada provides support for containers — such as vectors and sets — in its
standard library.
We present an introduction to containers here. For a list of all containers available in Ada,
see Appendix B (page 267).
19.1 Vectors
In the following sections, we present a general overview of vectors, including instantiation,
initialization, and operations on vector elements and vectors.
19.1.1 Instantiation
Here's an example showing the instantiation and declaration of a vector V:
Listing 1: show_vector_inst.adb
1 with Ada.Containers.Vectors;
2
3 procedure Show_Vector_Inst is
4
10 V : Integer_Vectors.Vector;
11 begin
12 null;
13 end Show_Vector_Inst;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_Inst
MD5: 8b737842d2784f25502990f21e1cf6de
Containers are based on generic packages, so we can't simply declare a vector as we would
declare an array of a specific type:
Instead, we first need to instantiate one of those packages. We with the container pack-
age (Ada.Containers.Vectors in this case) and instantiate it to create an instance of the
199
Introduction to Ada
generic package for the desired type. Only then can we declare the vector using the type
from the instantiated package. This instantiation needs to be done for any container type
from the standard library.
In the instantiation of Integer_Vectors, we indicate that the vector contains elements of
Integer type by specifying it as the Element_Type. By setting Index_Type to Natural, we
specify that the allowed range includes all natural numbers. We could have used a more
restrictive range if desired.
19.1.2 Initialization
One way to initialize a vector is from a concatenation of elements. We use the & operator,
as shown in the following example:
Listing 2: show_vector_init.adb
1 with Ada.Containers; use Ada.Containers;
2 with Ada.Containers.Vectors;
3
6 procedure Show_Vector_Init is
7
13 use Integer_Vectors;
14
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_Init
MD5: 0087b0a15e0c88b27ac36c3b27159a17
Runtime output
We specify use Integer_Vectors, so we have direct access to the types and operations
from the instantiated package. Also, the example introduces another operation on the
vector: Length, which retrieves the number of elements in the vector. We can use the dot
notation because Vector is a tagged type, allowing us to write either V.Length or Length
(V).
Listing 3: show_vector_append.adb
1 with Ada.Containers; use Ada.Containers;
2 with Ada.Containers.Vectors;
3
6 procedure Show_Vector_Append is
7
13 use Integer_Vectors;
14
15 V : Vector;
16 begin
17 Put_Line ("Appending some elements "
18 & "to the vector...");
19 V.Append (20);
20 V.Append (10);
21 V.Append (0);
22 V.Append (13);
23 Put_Line ("Finished appending.");
24
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_Append
MD5: f88d393ba96a7950f58d9f1c0c74a021
Runtime output
This example puts elements into the vector in the following sequence: (100, 40, 30, 20, 10,
0, 13).
The Reference Manual specifies that the worst-case complexity must be:
• O(log N) for the Append operation, and
• O(N log N) for the Prepend operation.
Listing 4: show_vector_first_last_element.adb
1 with Ada.Containers; use Ada.Containers;
2 with Ada.Containers.Vectors;
3
6 procedure Show_Vector_First_Last_Element is
7
13 use Integer_Vectors;
14
26 -- Using V.First_Element to
27 -- retrieve first element
28 Put_Line ("First element is "
29 & Img (V.First_Element));
30
31 -- Using V.Last_Element to
32 -- retrieve last element
33 Put_Line ("Last element is "
34 & Img (V.Last_Element));
35 end Show_Vector_First_Last_Element;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_First_Last_Element
MD5: 602255760d0017ced6b4115c845cd48d
Runtime output
You can swap elements by calling the procedure Swap and retrieving a reference (a cursor)
to the first and last elements of the vector by calling First and Last. A cursor allows us to
iterate over a container and process individual elements from it.
With these operations, we're able to write code to swap the first and last elements of a
vector:
Listing 5: show_vector_first_last_element.adb
1 with Ada.Containers; use Ada.Containers;
2 with Ada.Containers.Vectors;
3
6 procedure Show_Vector_First_Last_Element is
7
13 use Integer_Vectors;
14
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_First_Last_Element
MD5: 1a0c0bf28bb661b3f328473ac3c2eb54
Runtime output
19.1.5 Iterating
The easiest way to iterate over a container is to use a for E of Our_Container loop. This
gives us a reference (E) to the element at the current position. We can then use E directly.
For example:
Listing 6: show_vector_iteration.adb
1 with Ada.Containers.Vectors;
2
5 procedure Show_Vector_Iteration is
6
12 use Integer_Vectors;
13
21 --
22 -- Using for ... of loop to iterate:
23 --
24 for E of V loop
25 Put_Line ("- " & Img (E));
26 end loop;
27
28 end Show_Vector_Iteration;
Runtime output
Vector elements are:
- 20
- 10
- 0
- 13
We can also use indices to access vector elements. The format is similar to a loop over array
elements: we use a for I in <range> loop. The range is provided by V.First_Index and
V.Last_Index. We can access the current element by using it as an array index: V (I).
For example:
Listing 7: show_vector_index_iteration.adb
1 with Ada.Containers.Vectors;
2
5 procedure Show_Vector_Index_Iteration is
6
12 use Integer_Vectors;
(continues on next page)
18 --
19 -- Using indices in a "for I in ..." loop
20 -- to iterate:
21 --
22 for I in V.First_Index .. V.Last_Index loop
23 -- Displaying current index I
24 Put ("- ["
25 & Extended_Index'Image (I)
26 & "] ");
27
34 New_Line;
35 end loop;
36
37 end Show_Vector_Index_Iteration;
Runtime output
Vector elements are:
- [ 0] 20
- [ 1] 10
- [ 2] 0
- [ 3] 13
Here, in addition to displaying the vector elements, we're also displaying each index, I, just
like what we can do for array indices. Also, we can access the element by using either the
short form V (I) or the longer form V.Element (I) but not V.I.
As mentioned in the previous section, you can use cursors to iterate over containers. For
this, use the function Iterate, which retrieves a cursor for each position in the vector. The
corresponding loop has the format for C in V.Iterate loop. Like the previous example
using indices, you can again access the current element by using the cursor as an array
index: V (C). For example:
Listing 8: show_vector_cursor_iteration.adb
1 with Ada.Containers.Vectors;
2
5 procedure Show_Vector_Cursor_Iteration is
6
12 use Integer_Vectors;
13
18 --
19 -- Use a cursor to iterate in a loop:
20 --
21 for C in V.Iterate loop
22 -- Using To_Index function to retrieve
23 -- the index for the cursor position
24 Put ("- ["
25 & Extended_Index'Image (To_Index (C))
26 & "] ");
27
34 New_Line;
35 end loop;
36
50 end Show_Vector_Cursor_Iteration;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_Cursor_Iteration
MD5: de789bbd2e1814aae3fb5213c99ac25c
Runtime output
Instead of accessing an element in the loop using V (C), we could also have used the longer
form Element (C). In this example, we're using the function To_Index to retrieve the index
corresponding to the current cursor.
As shown in the comments after the loop, we could also use a while ... loop to iterate
over the vector. In this case, we would start with a cursor for the first element (retrieved
by calling V.First) and then call Next (C) to retrieve a cursor for subsequent elements.
Next (C) returns No_Element when the cursor reaches the end of the vector.
You can directly modify the elements using a reference. This is what it looks like when using
both indices and cursors:
The Reference Manual requires that the worst-case complexity for accessing an element be
O(log N).
Another way of modifying elements of a vector is using a process procedure, which takes
an individual element and does some processing on it. You can call Update_Element and
pass both a cursor and an access to the process procedure. For example:
Listing 9: show_vector_update.adb
1 with Ada.Containers.Vectors;
2
5 procedure Show_Vector_Update is
6
12 use Integer_Vectors;
13
28 end Show_Vector_Update;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_Update
MD5: 5dcc3dd8020632a8ea2ce975ecd8f4da
5 procedure Show_Find_Vector_Element is
6
12 use Integer_Vectors;
13
Project: Courses.Intro_To_Ada.Standard_Library.Show_Find_Vector_Element
MD5: c3da01cd66c8705a7cbccae8390d5f81
Runtime output
As we saw in the previous section, we can directly access vector elements by using either
an index or cursor. However, an exception is raised if we try to access an element with an
invalid index or cursor, so we must check whether the index or cursor is valid before using
it to access an element. In our example, Find_Index or Find might not have found the
element in the vector. We check for this possibility by comparing the index to No_Index or
the cursor to No_Element. For example:
Instead of writing V (C) := 14, we could use the longer form V.Replace_Element (C,
14).
6 procedure Show_Vector_Insert is
7
13 use Integer_Vectors;
14
35 New_Line;
36 Put_Line ("Adding element with value 9");
37 Put_Line (" (before 10)...");
38
39 --
40 -- Using V.Insert to insert the element
41 -- into the vector
42 --
43 C := V.Find (10);
44 if C /= No_Element then
45 V.Insert (C, 9);
46 end if;
47
48 Show_Elements (V);
49
50 end Show_Vector_Insert;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_Insert
MD5: af49f390388896c51ab97541036fbcaf
Runtime output
In this example, we're looking for an element with the value of 10. If we find it, we insert
an element with the value of 9 before it.
5 procedure Show_Remove_Vector_Element is
6 package Integer_Vectors is new
7 Ada.Containers.Vectors
8 (Index_Type => Natural,
9 Element_Type => Integer);
10
11 use Integer_Vectors;
12
37 end Show_Remove_Vector_Element;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Remove_Vector_Element
MD5: 540d0dc5715e58926e9dc4600bd6ad5d
We can extend this approach to delete all elements matching a certain value. We just need
to keep searching for the element in a loop until we get an invalid index or cursor. For
example:
6 procedure Show_Remove_Vector_Elements is
7
13 use Integer_Vectors;
14
34 --
35 -- Remove elements using an index
36 --
37 declare
(continues on next page)
52 --
53 -- Remove elements using a cursor
54 --
55 declare
56 E : constant Integer := 13;
57 C : Cursor;
58 begin
59 New_Line;
60 Put_Line
61 ("Removing all elements with value of "
62 & Integer'Image (E) & "...");
63 loop
64 C := V.Find (E);
65 exit when C = No_Element;
66 V.Delete (C);
67 end loop;
68 end;
69
70 Show_Elements (V);
71 end Show_Remove_Vector_Elements;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Remove_Vector_Elements
MD5: 6e364843b9638224bd9a36eb9d45e446
Runtime output
In this example, we remove all elements with the value 10 from the vector by retrieving
their index. Likewise, we remove all elements with the value 13 by retrieving their cursor.
6 procedure Show_Vector_Ops is
7
13 package Integer_Vectors_Sorting is
14 new Integer_Vectors.Generic_Sorting;
15
16 use Integer_Vectors;
17 use Integer_Vectors_Sorting;
18
40 New_Line;
41 Put_Line ("---- V1 ----");
42 Show_Elements (V1);
43
44 New_Line;
45 Put_Line ("---- V2 ----");
46 Show_Elements (V2);
47
48 New_Line;
49 Put_Line ("---- V3 ----");
50 Show_Elements (V3);
51
52 New_Line;
53 Put_Line
54 ("Concatenating V1, V2 and V3 into V:");
55
58 Show_Elements (V);
59
60 New_Line;
61 Put_Line ("Sorting V:");
62
63 Sort (V);
64
65 Show_Elements (V);
66
67 New_Line;
68 Put_Line ("Merging V2 into V1:");
69
72 Show_Elements (V1);
73
74 end Show_Vector_Ops;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Vector_Ops
MD5: 3301513e4e7fd2f28488966e5b24e448
Runtime output
---- V1 ----
---- V3 ----
Sorting V:
The Reference Manual requires that the worst-case complexity of a call to Sort be O(N2 )
and the average complexity be better than O(N2 ).
19.2 Sets
Sets are another class of containers. While vectors allow duplicated elements to be in-
serted, sets ensure that no duplicated elements exist.
In the following sections, we'll see operations you can perform on sets. However, since
many of the operations on vectors are similar to the ones used for sets, we'll cover them
more quickly here. Please refer back to the section on vectors for a more detailed discus-
sion.
6 procedure Show_Set_Init is
7
12 use Integer_Sets;
13
14 S : Set;
15 -- Same as: S : Integer_Sets.Set;
16 C : Cursor;
17 Ins : Boolean;
18 begin
19 S.Insert (20);
20 S.Insert (10);
21 S.Insert (0);
22 S.Insert (13);
23
48 --
49 -- Iterate over set using for .. of loop
50 --
51 Put_Line ("Elements:");
52 for E of S loop
53 Put_Line ("- " & Integer'Image (E));
54 end loop;
55 end Show_Set_Init;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Set_Init
MD5: b87f6729fea278396347248b95a30cb6
Runtime output
12 use Integer_Sets;
13
26 S : Set;
27 begin
28 S.Insert (20);
29 S.Insert (10);
30 S.Insert (0);
31 S.Insert (13);
32
33 S.Delete (13);
34
52 Show_Elements (S);
53 end Show_Set_Element_Ops;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Set_Element_Ops
MD5: 77fb2aaba4221e337b0f90dd1a49c556
Runtime output
In addition to ordered sets used in the examples above, the standard library also offers
hashed sets. The Reference Manual requires the following average complexity of each
operation:
6 procedure Show_Set_Ops is
7
12 use Integer_Sets;
13
38 S2.Insert (0);
39 S2.Insert (10);
40 S2.Insert (14);
41
42 S3.Insert (0);
43 S3.Insert (10);
44
45 New_Line;
46 Put_Line ("---- Set #1 ----");
47 Show_Elements (S1);
48
49 New_Line;
50 Put_Line ("---- Set #2 ----");
51 Show_Elements (S2);
52
53 New_Line;
54 Put_Line ("---- Set #3 ----");
55 Show_Elements (S3);
56
57 New_Line;
58 if S3.Is_Subset (S1) then
59 Put_Line ("S3 is a subset of S1");
60 else
61 Put_Line ("S3 is not a subset of S1");
62 end if;
63
64 S3 := S1 and S2;
65 Show_Op (S3, "Intersection");
66 Show_Elements (S3);
67
68 S3 := S1 or S2;
69 Show_Op (S3, "Union");
70 Show_Elements (S3);
71
72 S3 := S1 - S2;
73 Show_Op (S3, "Difference");
74 Show_Elements (S3);
75
76 S3 := S1 xor S2;
77 Show_Op (S3, "Symmetric difference");
78 Show_Elements (S3);
79
Runtime output
S3 is a subset of S1
® In other languages
Hashed maps are similar to dictionaries in Python and hashes in Perl. One of the main
differences is that these scripting languages allow using different types for the values
contained in a single map, while in Ada, both the type of key and value are specified in
the package instantiation and remains constant for that specific map. You can't have a
map where two elements are of different types or two keys are of different types. If you
want to use multiple types, you must create a different map for each and use only one
type in each map.
6 procedure Show_Hashed_Map is
7
15 use Integer_Hashed_Maps;
16
17 M : Map;
18 -- Same as:
(continues on next page)
42 end Show_Hashed_Map;
Runtime output
Alice's age is 24
5 procedure Show_Ordered_Map is
6
12 use Integer_Ordered_Maps;
13
14 M : Map;
15 begin
16 M.Include ("Alice", 24);
17 M.Include ("John", 40);
18 M.Include ("Bob", 28);
19
35 end Show_Ordered_Map;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Ordered_Map
MD5: 3deb3c685e767cee271b06e87727b086
Runtime output
Alice's age is 24
You can see a great similarity between the examples above and from the previous section.
In fact, since both kinds of maps share many operations, we didn't need to make extensive
modifications when we changed our example to use ordered maps instead of hashed maps.
The main difference is seen when we run the examples: the output of a hashed map is
usually unordered, but the output of a ordered map is always ordered, as implied by its
name.
19.3.3 Complexity
Hashed maps are generally the fastest data structure available to you in Ada if you need to
associate heterogeneous keys to values and search for them quickly. In most cases, they
are slightly faster than ordered maps. So if you don't need ordering, use hashed maps.
The Reference Manual requires the following average complexity of operations:
TWENTY
The standard library supports processing of dates and times using two approaches:
• Calendar approach, which is suitable for handling dates and times in general;
• Real-time approach, which is better suited for real-time applications that require en-
hanced precision — for example, by having access to an absolute clock and handling
time spans. Note that this approach only supports times, not dates.
The following sections present these two approaches.
Listing 1: display_current_time.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Calendar; use Ada.Calendar;
3
4 with Ada.Calendar.Formatting;
5 use Ada.Calendar.Formatting;
6
7 procedure Display_Current_Time is
8 Now : Time := Clock;
9 begin
10 Put_Line ("Current time: " & Image (Now));
11 end Display_Current_Time;
Project: Courses.Intro_To_Ada.Standard_Library.Display_Current_Time
MD5: 4a88069b33ecf80314b0164a472ff606
Runtime output
This example displays the current date and time, which is retrieved by a call to the Clock
function. We call the function Image from the Ada.Calendar.Formatting package to get
a String for the current date and time. We could instead retrieve each component using
the Split function. For example:
Listing 2: display_current_year.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Calendar; use Ada.Calendar;
(continues on next page)
227
Introduction to Ada
4 procedure Display_Current_Year is
5 Now : Time := Clock;
6
7 Now_Year : Year_Number;
8 Now_Month : Month_Number;
9 Now_Day : Day_Number;
10 Now_Seconds : Day_Duration;
11 begin
12 Split (Now,
13 Now_Year,
14 Now_Month,
15 Now_Day,
16 Now_Seconds);
17
Project: Courses.Intro_To_Ada.Standard_Library.Display_Current_Year
MD5: fdf298ee97f225261ce3839ebd833bbe
Runtime output
Listing 3: display_delay_next_specific_time.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Calendar; use Ada.Calendar;
3
4 with Ada.Calendar.Formatting;
5 use Ada.Calendar.Formatting;
6
7 with Ada.Calendar.Time_Zones;
8 use Ada.Calendar.Time_Zones;
9
10 procedure Display_Delay_Next_Specific_Time is
11 TZ : Time_Offset := UTC_Time_Offset;
12 Next : Time :=
13 Ada.Calendar.Formatting.Time_Of
14 (Year => 2018,
15 Month => 5,
16 Day => 1,
(continues on next page)
Project: Courses.Intro_To_Ada.Standard_Library.Display_Delay_Next_Specific_Time
MD5: 36ec2bdce7c1e8d107fae54ef9852d3f
Runtime output
In this example, we specify the date and time by initializing Next using a call to Time_Of,
a function taking the various components of a date (year, month, etc) and returning an
element of the Time type. Because the date specified is in the past, the delay until
statement won't produce any noticeable effect. However, if we passed a date in the future,
the program would wait until that specific date and time arrived.
Here we're converting the time to the local timezone. If we don't specify a timezone, Co-
ordinated Universal Time (abbreviated to UTC) is used by default. By retrieving the time
offset to UTC with a call to UTC_Time_Offset from the Ada.Calendar.Time_Zones package,
we can initialize TZ and use it in the call to Time_Of. This is all we need do to make the
information provided to Time_Of relative to the local time zone.
We could achieve a similar result by initializing Next with a String. We can do this with a
call to Value from the Ada.Calendar.Formatting package. This is the modified code:
Listing 4: display_delay_next_specific_time.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Calendar; use Ada.Calendar;
3
4 with Ada.Calendar.Formatting;
5 use Ada.Calendar.Formatting;
6
7 with Ada.Calendar.Time_Zones;
8 use Ada.Calendar.Time_Zones;
9
10 procedure Display_Delay_Next_Specific_Time is
11 TZ : Time_Offset := UTC_Time_Offset;
12 Next : Time :=
13 Ada.Calendar.Formatting.Value
14 ("2018-05-01 15:00:00.00", TZ);
15
Project: Courses.Intro_To_Ada.Standard_Library.Display_Delay_Next_Specific_Time
MD5: fdf6ad7fca303d4d7bd444c23e11c7bd
Runtime output
In this example, we're again using TZ in the call to Value to adjust the input time to the
current time zone.
In the examples above, we were delaying to a specific date and time. Just like we saw in
the tasking chapter, we could instead specify the delay relative to the current time. For
example, we could delay by 5 seconds, using the current time:
Listing 5: display_delay_next.adb
1 with Ada.Calendar; use Ada.Calendar;
2 with Ada.Text_IO; use Ada.Text_IO;
3
4 procedure Display_Delay_Next is
5 D : Duration := 5.0;
6 -- ^ seconds
7 Now : Time := Clock;
8 Next : Time := Now + D;
9 -- ^ use duration to
10 -- specify next
11 -- point in time
12 begin
13 Put_Line ("Let's wait "
14 & Duration'Image (D)
15 & " seconds...");
16 delay until Next;
17 Put_Line ("Enough waiting!");
18 end Display_Delay_Next;
Project: Courses.Intro_To_Ada.Standard_Library.Display_Delay_Next
MD5: 58360d93388c3fe027c3d9d67389efc7
Runtime output
Here, we're specifying a duration of 5 seconds in D, adding it to the current time from Now,
and storing the sum in Next. We then use it in the delay until statement.
20.2 Real-time
In addition to Ada.Calendar, the standard library also supports time operations for real-time
applications. These are included in the Ada.Real_Time package. This package also include
a Time type. However, in the Ada.Real_Time package, the Time type is used to represent
an absolute clock and handle a time span. This contrasts with the Ada.Calendar, which
uses the Time type to represent dates and times.
In the previous section, we used the Time type from the Ada.Calendar and the delay until
statement to delay an application by 5 seconds. We could have used the Ada.Real_Time
package instead. Let's modify that example:
Listing 6: display_delay_next_real_time.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Real_Time; use Ada.Real_Time;
3
4 procedure Display_Delay_Next_Real_Time is
5 D : Time_Span := Seconds (5);
6 Next : Time := Clock + D;
7 begin
8 Put_Line ("Let's wait "
9 & Duration'Image (To_Duration (D))
10 & " seconds...");
11 delay until Next;
12 Put_Line ("Enough waiting!");
13 end Display_Delay_Next_Real_Time;
Project: Courses.Intro_To_Ada.Standard_Library.Display_Delay_Next_Real_Time
MD5: a80e96c4ac7bd3ba7813f983b10cb038
Runtime output
The main difference is that D is now a variable of type Time_Span, defined in the Ada.
Real_Time package. We call the function Seconds to initialize D, but could have gotten a
finer granularity by calling Nanoseconds instead. Also, we need to first convert D to the
Duration type using To_Duration before we can display it.
20.2.1 Benchmarking
One interesting application using the Ada.Real_Time package is benchmarking. We've
used that package before in a previous section when discussing tasking. Let's look at an
example of benchmarking:
Listing 7: display_benchmarking.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Real_Time; use Ada.Real_Time;
3
4 procedure Display_Benchmarking is
5
6 procedure Computational_Intensive_App is
7 begin
8 delay 5.0;
9 end Computational_Intensive_App;
(continues on next page)
14 begin
15 Start_Time := Clock;
16
17 Computational_Intensive_App;
18
19 Stop_Time := Clock;
20 Elapsed_Time := Stop_Time - Start_Time;
21
Project: Courses.Intro_To_Ada.Standard_Library.Display_Benchmarking
MD5: 4b20940cb613d3f634be5224f409efeb
Runtime output
Listing 8: display_benchmarking_cpu_time.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Real_Time; use Ada.Real_Time;
3 with Ada.Execution_Time; use Ada.Execution_Time;
4
5 procedure Display_Benchmarking_CPU_Time is
6
7 procedure Computational_Intensive_App is
8 begin
9 delay 5.0;
10 end Computational_Intensive_App;
11
15 begin
16 Start_Time := Clock;
17
18 Computational_Intensive_App;
19
20 Stop_Time := Clock;
21 Elapsed_Time := Stop_Time - Start_Time;
22
Runtime output
CPU time: 0.000128226 seconds
In this example, Start_Time and Stop_Time are of type CPU_Time instead of Time. How-
ever, we still call the Clock function to initialize both variables and calculate the elapsed
time in the same way as before. By running this program, we see that the CPU time is sig-
nificantly lower than the 5 seconds we've seen before. This is because the delay statement
doesn't require much CPU time. The results will be different if we change the implementa-
tion of Computational_Intensive_App to use a mathematical function in a long loop. For
example:
Listing 9: display_benchmarking_math.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Real_Time; use Ada.Real_Time;
3 with Ada.Execution_Time; use Ada.Execution_Time;
4
5 with Ada.Numerics.Generic_Elementary_Functions;
6
7 procedure Display_Benchmarking_Math is
8
9 procedure Computational_Intensive_App is
10 package Funcs is new
11 Ada.Numerics.Generic_Elementary_Functions
12 (Float_Type => Long_Long_Float);
13 use Funcs;
14
15 X : Long_Long_Float;
16 begin
17 for I in 0 .. 1_000_000 loop
18 X := Tan (Arctan
19 (Tan (Arctan
20 (Tan (Arctan
21 (Tan (Arctan
22 (Tan (Arctan
23 (Tan (Arctan
24 (0.577))))))))))));
25 end loop;
26 end Computational_Intensive_App;
27
28 procedure Benchm_Elapsed_Time is
29 Start_Time, Stop_Time : Time;
30 Elapsed_Time : Time_Span;
31
32 begin
33 Start_Time := Clock;
34
35 Computational_Intensive_App;
36
37 Stop_Time := Clock;
38 Elapsed_Time := Stop_Time - Start_Time;
(continues on next page)
46 procedure Benchm_CPU_Time is
47 Start_Time, Stop_Time : CPU_Time;
48 Elapsed_Time : Time_Span;
49
50 begin
51 Start_Time := Clock;
52
53 Computational_Intensive_App;
54
55 Stop_Time := Clock;
56 Elapsed_Time := Stop_Time - Start_Time;
57
Project: Courses.Intro_To_Ada.Standard_Library.Display_Benchmarking_Math
MD5: 06fe96bf03321c248dd1ed843648cf0b
Runtime output
TWENTYONE
In previous chapters, we've seen source-code examples using the String type, which is a
fixed-length string type — essentialy, it's an array of characters. In many cases, this data
type is good enough to deal with textual information. However, there are situations that
require more advanced text processing. Ada offers alternative approaches for these cases:
• Bounded strings: similar to fixed-length strings, bounded strings have a maximum
length, which is set at its instantiation. However, bounded strings are not arrays of
characters. At any time, they can contain a string of varied length — provided this
length is below or equal to the maximum length.
• Unbounded strings: similar to bounded strings, unbounded strings can contain strings
of varied length. However, in addition to that, they don't require a maximum length
to be specified at the declaration of a string. In this sense, they are very flexible.
Although we don't specify a maximum length for unbounded strings, the limit is defined
by the Reference Manual21 :
An object of type Unbounded_String represents a String whose low bound is
1 and whose length can vary conceptually between 0 and Natural'Last.
Therefore, the implicit maximum length is Natural'Last. In contrast, bounded strings
have an explicit maximum length that is specified when the Generic_Bounded_Length
package is instantiated (as we'll see later on (page 240)).
Another difference between bounded and unbounded strings is the strategy that is used
by the compiler to allocate memory for those strings. When using GNAT, bounded strings
are allocated on the stack, while unbounded strings are allocated on the heap.
The following sections present an overview of the different string types and common oper-
ations for string types.
235
Introduction to Ada
Listing 1: show_find_substring.adb
1 with Ada.Strings.Fixed; use Ada.Strings.Fixed;
2 with Ada.Text_IO; use Ada.Text_IO;
3
4 procedure Show_Find_Substring is
5
19 Idx := 0;
20 for I in 1 .. Cnt loop
21 Idx := Index
22 (Source => S,
23 Pattern => P,
24 From => Idx + 1);
25
31 end Show_Find_Substring;
Runtime output
String: Hello World World World
Count for 'World': 3
Found instance of 'World' at position: 7
Found instance of 'World' at position: 13
Found instance of 'World' at position: 19
We initialize the string S using a multiplication. Writing "Hello" & 3 * " World" creates
the string Hello World World World. We then call the function Count to get the number
of instances of the word World in S. Next we call the function Index in a loop to find the
index of each instance of World in S.
That example looked for instances of a specific substring. In the next example, we retrieve
all the words in the string. We do this using Find_Token and specifying whitespaces as
separators. For example:
Listing 2: show_find_words.adb
1 with Ada.Strings; use Ada.Strings;
2 with Ada.Strings.Fixed; use Ada.Strings.Fixed;
3 with Ada.Strings.Maps; use Ada.Strings.Maps;
4 with Ada.Text_IO; use Ada.Text_IO;
(continues on next page)
6 procedure Show_Find_Words is
7
29 exit when L = 0;
30
36 I := L + 1;
37 end loop;
38 end Show_Find_Words;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Find_Words
MD5: e622f489af5901e5d31f314efc3324d2
Runtime output
Operation Description
Insert Insert substring in a string
Overwrite Overwrite a string with a substring
Delete Delete a substring
Trim Remove whitespaces from a string
All these operations are available both as functions or procedures. Functions create a new
string but procedures perform the operations in place. The procedure will raise an excep-
tion if the constraints of the string are not satisfied. For example, if we have a string S
containing 10 characters, inserting a string with two characters (e.g. "!!") into it produces
a string containing 12 characters. Since it has a fixed length, we can't increase its size. One
possible solution in this case is to specify that truncation should be applied while inserting
the substring. This keeps the length of S fixed. Let's see an example that makes use of
both function and procedure versions of Insert, Overwrite, and Delete:
Listing 3: show_adapted_strings.adb
1 with Ada.Strings; use Ada.Strings;
2 with Ada.Strings.Fixed; use Ada.Strings.Fixed;
3 with Ada.Text_IO; use Ada.Text_IO;
4
5 procedure Show_Adapted_Strings is
6
11 procedure Display_Adapted_String
12 (Source : String;
13 Before : Positive;
14 New_Item : String;
15 Pattern : String)
16 is
17 S_Ins_In : String := Source;
18 S_Ovr_In : String := Source;
19 S_Del_In : String := Source;
20
21 S_Ins : String :=
22 Insert (Source,
23 Before,
24 New_Item & " ");
25 S_Ovr : String :=
26 Overwrite (Source,
27 Before,
28 New_Item);
29 S_Del : String :=
30 Trim (Delete (Source,
31 Before,
32 Before +
33 Pattern'Length - 1),
34 Ada.Strings.Right);
35 begin
36 Insert (S_Ins_In,
37 Before,
38 New_Item,
39 Right);
40
41 Overwrite (S_Ovr_In,
42 Before,
(continues on next page)
46 Delete (S_Del_In,
47 Before,
48 Before + Pattern'Length - 1);
49
68 Idx : Natural;
69 begin
70 Idx := Index
71 (Source => S,
72 Pattern => P);
73
Project: Courses.Intro_To_Ada.Standard_Library.Show_Adapted_Strings
MD5: b31b6bc94d8bdbec717c6b6b2534beb6
Runtime output
In this example, we look for the index of the substring World and perform operations on
this substring within the outer string. The procedure Display_Adapted_String uses both
versions of the operations. For the procedural version of Insert and Overwrite, we apply
truncation to the right side of the string (Right). For the Delete procedure, we specify
the range of the substring, which is replaced by whitespaces. For the function version of
Delete, we also call Trim which trims the trailing whitespace.
Listing 4: show_char_array.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Char_Array is
4 S : String (1 .. 15);
5 -- Strings are arrays of Character
6 begin
7 S := "Hello ";
8 -- Alternatively:
9 --
10 -- #1:
11 -- S (1 .. 5) := "Hello";
12 -- S (6 .. S'Last) := (others => ' ');
13 --
14 -- #2:
15 -- S := ('H', 'e', 'l', 'l', 'o',
16 -- others => ' ');
17
Project: Courses.Intro_To_Ada.Standard_Library.Show_Char_Array
MD5: 9f3df03c9c5336184139cf2a22f2cb7e
Runtime output
String: Hello
String Length: 15
In this case, we can't simply write S := "Hello" because the resulting array of characters
for the Hello constant has a different length than the S string. Therefore, we need to
include trailing whitespaces to match the length of S. As shown in the example, we could
use an exact range for the initialization ( S (1 .. 5)) or use an explicit array of individual
characters.
When strings are initialized or manipulated at run-time, it's usually better to use bounded
or unbounded strings. An important feature of these types is that they aren't arrays, so the
difficulties presented above don't apply. Let's start with bounded strings.
ever, bounded strings are not arrays, so initializing them at run-time is much easier. For
example:
Listing 5: show_bounded_string.adb
1 with Ada.Strings; use Ada.Strings;
2 with Ada.Strings.Bounded;
3 with Ada.Text_IO; use Ada.Text_IO;
4
5 procedure Show_Bounded_String is
6 package B_Str is new
7 Ada.Strings.Bounded.Generic_Bounded_Length
8 (Max => 15);
9 use B_Str;
10
11 S1, S2 : Bounded_String;
12
13 procedure Display_String_Info
14 (S : Bounded_String)
15 is
16 begin
17 Put_Line ("String: " & To_String (S));
18 Put_Line ("String Length: "
19 & Integer'Image (Length (S)));
20 -- String:
21 -- S'Length => ok
22 -- Bounded_String:
23 -- S'Length => compilation error:
24 -- bounded strings are
25 -- not arrays!
26
31 begin
32 S1 := To_Bounded_String ("Hello");
33 Display_String_Info (S1);
34
38 S1 := To_Bounded_String
39 ("Something longer to say here...",
40 Right);
41 Display_String_Info (S1);
42 end Show_Bounded_String;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Bounded_String
MD5: a51fdeacfd43923145ee92bf5c72ecd6
Runtime output
String: Hello
String Length: 5
Max. Length: 15
String: Hello World
String Length: 11
Max. Length: 15
String: Something longe
(continues on next page)
By using bounded strings, we can easily assign to S1 and S2 multiple times during execu-
tion. We use the To_Bounded_String and To_String functions to convert, in the respective
direction, between fixed-length and bounded strings. A call to To_Bounded_String raises
an exception if the length of the input string is greater than the maximum capacity of the
bounded string. To avoid this, we can use the truncation parameter (Right in our example).
Bounded strings are not arrays, so we can't use the 'Length attribute as we did for fixed-
length strings. Instead, we call the Length function, which returns the length of the bounded
string. The Max_Length constant represents the maximum length of the bounded string that
we set when we instantiated the package.
After initializing a bounded string, we can manipulate it. For example, we can append
a string to a bounded string using Append or concatenate bounded strings using the &
operator. Like so:
Listing 6: show_bounded_string_op.adb
1 with Ada.Strings; use Ada.Strings;
2 with Ada.Strings.Bounded;
3 with Ada.Text_IO; use Ada.Text_IO;
4
5 procedure Show_Bounded_String_Op is
6 package B_Str is new
7 Ada.Strings.Bounded.Generic_Bounded_Length
8 (Max => 30);
9 use B_Str;
10
11 S1, S2 : Bounded_String;
12 begin
13 S1 := To_Bounded_String ("Hello");
14 -- Alternatively:
15 --
16 -- A := Null_Bounded_String & "Hello";
17
24 S2 := To_Bounded_String ("Hello!");
25 S1 := S1 & " " & S2;
26 Put_Line ("String: " & To_String (S1));
27 end Show_Bounded_String_Op;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Bounded_String_Op
MD5: c7c6a840c314a9cd9f75aac082a63159
Runtime output
We can initialize a bounded string with an empty string using the Null_Bounded_String
constant. Also, we can use the Append procedure and specify the truncation mode like we
do with the To_Bounded_String function.
Listing 7: show_unbounded_string.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Strings; use Ada.Strings;
3
4 with Ada.Strings.Unbounded;
5 use Ada.Strings.Unbounded;
6
7 procedure Show_Unbounded_String is
8 S1, S2 : Unbounded_String;
9
10 procedure Display_String_Info
11 (S : Unbounded_String)
12 is
13 begin
14 Put_Line ("String: " & To_String (S));
15 Put_Line ("String Length: "
16 & Integer'Image (Length (S)));
17 end Display_String_Info;
18 begin
19 S1 := To_Unbounded_String ("Hello");
20 -- Alternatively:
21 --
22 -- A := Null_Unbounded_String & "Hello";
23
24 Display_String_Info (S1);
25
29 S1 := To_Unbounded_String
30 ("Something longer to say here...");
31 Display_String_Info (S1);
32 end Show_Unbounded_String;
Runtime output
String: Hello
String Length: 5
String: Hello World
(continues on next page)
Like bounded strings, we can assign to S1 and S2 multiple times during execution and use
the To_Unbounded_String and To_String functions to convert back-and-forth between
fixed-length strings and unbounded strings. However, in this case, truncation is not needed.
And, just like for bounded strings, you can use the Append procedure and the & operator for
unbounded strings. For example:
Listing 8: show_unbounded_string_op.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 with Ada.Strings.Unbounded;
4 use Ada.Strings.Unbounded;
5
6 procedure Show_Unbounded_String_Op is
7 S1, S2 : Unbounded_String :=
8 Null_Unbounded_String;
9 begin
10 S1 := S1 & "Hello";
11 S2 := S2 & "Hello!";
12
Project: Courses.Intro_To_Ada.Standard_Library.Show_Unbounded_String_Op
MD5: 806e24a6dd0bc87e76f73a22e42ba390
Runtime output
In this example, we're concatenating the unbounded S1 and S2 strings with the "Hello"
and "Hello!" strings, respectively. Also, we're using the Append procedure, just like we did
with bounded strings.
TWENTYTWO
Listing 1: show_std_text_out.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Std_Text_Out is
4 begin
5 Put_Line (Standard_Output, "Hello World #1");
6 Put_Line (Standard_Error, "Hello World #2");
7 end Show_Std_Text_Out;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Std_Text_Out
MD5: 4d75bd2906226897244e3d2a611c9725
Runtime output
245
Introduction to Ada
Hello World #1
Hello World #2
You can also use this parameter to write information to any text file. To create a new file for
writing, use the Create procedure, which initializes a File_Type element that you can later
pass to Put_Line (instead of, e.g., Standard_Output). After you finish writing information,
you can close the file by calling the Close procedure.
You use a similar method to read information from a text file. However, when opening the
file, you must specify that it's an input file (In_File) instead of an output file. Also, instead
of calling the Put_Line procedure, you call the Get_Line function to read information from
the file.
Let's see an example that writes information into a new text file and then reads it back from
the same file:
Listing 2: show_simple_text_file_io.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Simple_Text_File_IO is
4 F : File_Type;
5 File_Name : constant String := "simple.txt";
6 begin
7 Create (F, Out_File, File_Name);
8 Put_Line (F, "Hello World #1");
9 Put_Line (F, "Hello World #2");
10 Put_Line (F, "Hello World #3");
11 Close (F);
12
Runtime output
Hello World #1
Hello World #2
Hello World #3
In addition to the Create and Close procedures, the standard library also includes a Reset
procedure, which, as the name implies, resets (erases) all the information from the file. For
example:
Listing 3: show_text_file_reset.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Text_File_Reset is
4 F : File_Type;
5 File_Name : constant String := "simple.txt";
6 begin
7 Create (F, Out_File, File_Name);
8 Put_Line (F, "Hello World #1");
(continues on next page)
Project: Courses.Intro_To_Ada.Standard_Library.Show_Text_File_Reset
MD5: 5e5498f03b2c829513af062c5959fc93
Runtime output
Hello World #2
By running this program, we notice that, although we've written the first string ("Hello
World #1") to the file, it has been erased because of the call to Reset.
In addition to opening a file for reading or writing, you can also open an existing file and
append to it. Do this by calling the Open procedure with the Append_File option.
When calling the Open procedure, an exception is raised if the specified file isn't found.
Therefore, you should handle exceptions in that context. The following example deletes a
file and then tries to open the same file for reading:
Listing 4: show_text_file_input_except.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 procedure Show_Text_File_Input_Except is
4 F : File_Type;
5 File_Name : constant String := "simple.txt";
6 begin
7 -- Open output file and delete it
8 Create (F, Out_File, File_Name);
9 Delete (F);
10
Project: Courses.Intro_To_Ada.Standard_Library.Show_Text_File_Input_Except
MD5: c8d257091831c48d10b6e70e34b4261b
Runtime output
In this example, we create the file by calling Create and then delete it by calling Delete.
After the call to Delete, we can no longer use the File_Type element. After deleting the
file, we try to open the non-existent file, which raises a Name_Error exception.
Listing 5: show_seq_float_io.adb
1 with Ada.Text_IO;
2 with Ada.Sequential_IO;
3
4 procedure Show_Seq_Float_IO is
5 package Float_IO is
6 new Ada.Sequential_IO (Float);
7 use Float_IO;
8
9 F : Float_IO.File_Type;
10 File_Name : constant String :=
11 "float_file.bin";
12 begin
13 Create (F, Out_File, File_Name);
14 Write (F, 1.5);
15 Write (F, 2.4);
16 Write (F, 6.7);
17 Close (F);
18
19 declare
20 Value : Float;
21 begin
22 Open (F, In_File, File_Name);
23 while not End_Of_File (F) loop
24 Read (F, Value);
25 Ada.Text_IO.Put_Line
26 (Float'Image (Value));
27 end loop;
28 Close (F);
29 end;
30 end Show_Seq_Float_IO;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Seq_Float_IO
MD5: 27aa5daf92cba5df23fdc55c3578aa34
Runtime output
1.50000E+00
2.40000E+00
6.70000E+00
We use the same approach to read and write complex information. The following example
Listing 6: show_seq_rec_io.adb
1 with Ada.Text_IO;
2 with Ada.Sequential_IO;
3
4 procedure Show_Seq_Rec_IO is
5 type Num_Info is record
6 Valid : Boolean := False;
7 Value : Float;
8 end record;
9
26 F : Num_Info_IO.File_Type;
27 File_Name : constant String :=
28 "float_file.bin";
29 begin
30 Create (F, Out_File, File_Name);
31 Write (F, (True, 1.5));
32 Write (F, (False, 2.4));
33 Write (F, (True, 6.7));
34 Close (F);
35
36 declare
37 Value : Num_Info;
38 begin
39 Open (F, In_File, File_Name);
40 while not End_Of_File (F) loop
41 Read (F, Value);
42 Put_Line (Value);
43 end loop;
44 Close (F);
45 end;
46 end Show_Seq_Rec_IO;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Seq_Rec_IO
MD5: a88b1428cc50745dce0509087e74adb7
Runtime output
(ok, 1.50000E+00)
(not ok, -----------)
(ok, 6.70000E+00)
As the example shows, we can use the same approach we used for floating-point types to
perform file I/O for this record. Once we instantiate the Ada.Sequential_IO package for
the record type, file I/O operations are performed the same way.
Listing 7: show_dir_float_io.adb
1 with Ada.Text_IO;
2 with Ada.Direct_IO;
3
4 procedure Show_Dir_Float_IO is
5 package Float_IO is new Ada.Direct_IO (Float);
6 use Float_IO;
7
8 F : Float_IO.File_Type;
9 File_Name : constant String :=
10 "float_file.bin";
11 begin
12 Create (F, Out_File, File_Name);
13 Write (F, 1.5);
14 Write (F, 2.4);
15 Write (F, 6.7);
16 Close (F);
17
18 declare
19 Value : Float;
20 begin
21 Open (F, In_File, File_Name);
22 while not End_Of_File (F) loop
23 Read (F, Value);
24 Ada.Text_IO.Put_Line
25 (Float'Image (Value));
26 end loop;
27 Close (F);
28 end;
29 end Show_Dir_Float_IO;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Dir_Float_IO
MD5: e4e5855976de44f53a821eb90dcbb206
Runtime output
1.50000E+00
2.40000E+00
6.70000E+00
Unlike sequential I/O, direct I/O allows you to access any position in the file. However, it
doesn't offer an option to append information to a file. Instead, it provides an Inout_File
mode allowing reading and writing to a file via the same File_Type element.
To access any position in the file, call the Set_Index procedure to set the new position /
index. You can use the Index function to retrieve the current index. Let's see an example:
Listing 8: show_dir_float_in_out_file.adb
1 with Ada.Text_IO;
2 with Ada.Direct_IO;
3
4 procedure Show_Dir_Float_In_Out_File is
5 package Float_IO is new Ada.Direct_IO (Float);
6 use Float_IO;
7
8 F : Float_IO.File_Type;
9 File_Name : constant String :=
10 "float_file.bin";
11 begin
12 -- Open file for input / output
13 Create (F, Inout_File, File_Name);
14 Write (F, 1.5);
15 Write (F, 2.4);
16 Write (F, 6.7);
17
23 declare
24 Value : Float;
25 begin
26 -- Set index to start of file
27 Set_Index (F, 1);
28
Project: Courses.Intro_To_Ada.Standard_Library.Show_Dir_Float_In_Out_File
MD5: 17b83a16ab8fa30f07cf8a0bd54078a1
Runtime output
1.50000E+00
2.40000E+00
7.70000E+00
By running this example, we see that the file contains 7.7, rather than the previous 6.7 that
we wrote. We overwrote the value by changing the index to the previous position before
doing another write.
In this example we used the Inout_File mode. Using that mode, we just changed the
index back to the initial position before reading from the file (Set_Index (F, 1)) instead
of closing the file and reopening it for reading.
Listing 9: show_float_stream.adb
1 with Ada.Text_IO;
2
3 with Ada.Streams.Stream_IO;
4 use Ada.Streams.Stream_IO;
5
6 procedure Show_Float_Stream is
7 F : File_Type;
8 S : Stream_Access;
9 File_Name : constant String :=
10 "float_file.bin";
11 begin
12 Create (F, Out_File, File_Name);
13 S := Stream (F);
14
19 Close (F);
20
21 declare
22 Value : Float;
23 begin
24 Open (F, In_File, File_Name);
25 S := Stream (F);
26
Project: Courses.Intro_To_Ada.Standard_Library.Show_Float_Stream
MD5: 34ccf04b0821074a332019ac0e38bb3e
Runtime output
1.50000E+00
2.40000E+00
6.70000E+00
After the call to Create, we retrieve the corresponding Stream_Access element by calling
the Stream function. We then use this stream to write information to the file via the 'Write
attribute of the Float type. After closing the file and reopening it for reading, we again
retrieve the corresponding Stream_Access element and processed to read information from
the file via the 'Read attribute of the Float type.
You can use streams to create and process files containing different data types within the
same file. You can also read and write unbounded data types such as strings. However,
when using unbounded data types you must call the 'Input and 'Output attributes of the
unbounded data type: these attributes write information about bounds or discriminants in
addition to the object's actual data.
The following example shows file I/O that mixes both strings of different lengths and floating-
point values:
3 with Ada.Streams.Stream_IO;
4 use Ada.Streams.Stream_IO;
5
6 procedure Show_String_Stream is
7 F : File_Type;
8 S : Stream_Access;
9 File_Name : constant String :=
10 "float_file.bin";
11
28 begin
29 Create (F, Out_File, File_Name);
30 S := Stream (F);
31
36 Close (F);
37
46 end Show_String_Stream;
Project: Courses.Intro_To_Ada.Standard_Library.Show_String_Stream
MD5: 3ae8276ada5f24cab49994e368e0fa34
Runtime output
When you use Stream I/O, no information is written into the file indicating the type of the
data that you wrote. If a file contains data from different types, you must reference types
in the same order when reading a file as when you wrote it. If not, the information you get
will be corrupted. Unfortunately, strong data typing doesn't help you in this case. Writing
simple procedures for file I/O (as in the example above) may help ensuring that the file
format is consistent.
Like direct I/O, stream I/O support also allows you to access any location in the file. However,
when doing so, you need to be extremely careful that the position of the new index is
consistent with the data types you're expecting.
TWENTYTHREE
The standard library provides support for common numeric operations on floating-point
types as well as on complex types and matrices. In the sections below, we present a brief
introduction to these numeric operations.
Listing 1: show_elem_math.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Numerics; use Ada.Numerics;
3
4 with Ada.Numerics.Elementary_Functions;
5 use Ada.Numerics.Elementary_Functions;
6
7 procedure Show_Elem_Math is
8 X : Float;
9 begin
10 X := 2.0;
11 Put_Line ("Square root of "
12 & Float'Image (X)
13 & " is "
14 & Float'Image (Sqrt (X)));
15
16 X := e;
17 Put_Line ("Natural log of "
18 & Float'Image (X)
19 & " is "
20 & Float'Image (Log (X)));
21
22 X := 10.0 ** 6.0;
23 Put_Line ("Log_10 of "
24 & Float'Image (X)
25 & " is "
26 & Float'Image (Log (X, 10.0)));
27
28 X := 2.0 ** 8.0;
29 Put_Line ("Log_2 of "
30 & Float'Image (X)
31 & " is "
32 & Float'Image (Log (X, 2.0)));
33
34 X := Pi;
(continues on next page)
255
Introduction to Ada
40 X := -1.0;
41 Put_Line ("Arccos of "
42 & Float'Image (X)
43 & " is "
44 & Float'Image (Arccos (X)));
45 end Show_Elem_Math;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Elem_Math
MD5: 17511d7e17cd98d4b6e49ad302d6dcb6
Runtime output
Here we use the standard e and Pi constants from the Ada.Numerics package.
The Ada.Numerics.Elementary_Functions package provides operations for the Float
type. Similar packages are available for Long_Float and Long_Long_Float types.
For example, the Ada.Numerics.Long_Elementary_Functions package offers the
same set of operations for the Long_Float type. In addition, the Ada.Numerics.
Generic_Elementary_Functions package is a generic version of the package that you
can instantiate for custom floating-point types. In fact, the Elementary_Functions pack-
age can be defined as follows:
Listing 2: show_float_random_num.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 with Ada.Numerics.Float_Random;
4 use Ada.Numerics.Float_Random;
5
6 procedure Show_Float_Random_Num is
7 G : Generator;
8 X : Uniformly_Distributed;
9 begin
10 Reset (G);
11
Runtime output
Some random numbers between 0.00000E+00 and 1.00000E+00:
8.36265E-01
4.96328E-01
8.25025E-01
9.49494E-01
8.07868E-01
4.71265E-02
9.08119E-01
4.49425E-01
4.80701E-01
2.01437E-01
4.59645E-01
6.92531E-01
4.80167E-01
6.49456E-01
6.68754E-01
The standard library also includes a random number generator for discrete numbers, which
is part of the Ada.Numerics.Discrete_Random package. Since it's a generic package, you
have to instantiate it for the desired discrete type. This allows you to specify a range for
the generator. In the following example, we create an application that displays random
integers between 1 and 10:
Listing 3: show_discrete_random_num.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Numerics.Discrete_Random;
3
4 procedure Show_Discrete_Random_Num is
5
8 package R is new
9 Ada.Numerics.Discrete_Random (Random_Range);
10 use R;
11
12 G : Generator;
13 X : Random_Range;
14 begin
15 Reset (G);
16
23 for I in 1 .. 15 loop
24 X := Random (G);
25 Put_Line (Integer'Image (X));
26 end loop;
27 end Show_Discrete_Random_Num;
Project: Courses.Intro_To_Ada.Standard_Library.Show_Discrete_Random_Num
MD5: 892f6525477f9a2c56f88885de011fba
Runtime output
Here, package R is instantiated with the Random_Range type, which has a constrained range
between 1 and 10. This allows us to control the range used for the random numbers.
We could easily modify the application to display random integers between 0 and 20 by
changing the specification of the Random_Range type. We can also use floating-point or
fixed-point types.
Listing 4: show_elem_math.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2 with Ada.Numerics; use Ada.Numerics;
3
4 with Ada.Numerics.Complex_Types;
5 use Ada.Numerics.Complex_Types;
(continues on next page)
7 with Ada.Numerics.Complex_Elementary_Functions;
8 use Ada.Numerics.Complex_Elementary_Functions;
9
10 with Ada.Text_IO.Complex_IO;
11
12 procedure Show_Elem_Math is
13
18 X, Y : Complex;
19 R, Th : Float;
20 begin
21 X := (2.0, -1.0);
22 Y := (3.0, 4.0);
23
24 Put (X);
25 Put (" * ");
26 Put (Y);
27 Put (" is ");
28 Put (X * Y);
29 New_Line;
30 New_Line;
31
32 R := 3.0;
33 Th := Pi / 2.0;
34 X := Compose_From_Polar (R, Th);
35 -- Alternatively:
36 -- X := R * Exp ((0.0, Th));
37 -- X := R * e ** Complex'(0.0, Th);
38
Project: Courses.Intro_To_Ada.Standard_Library.Show_Elem_Math
(continues on next page)
Runtime output
As we can see from this example, all the common operators, such as * and +, are available
for complex types. You also have typical operations on complex numbers, such as Argument
and Exp. In addition to initializing complex numbers in the cartesian form using aggregates,
you can do so from the polar form by calling the Compose_From_Polar function.
The Ada.Numerics.Complex_Types and Ada.Numerics.Complex_Elementary_Functions
packages provide operations for the Float type. Similar packages are avail-
able for Long_Float and Long_Long_Float types. In addition, the Ada.Numerics.
Generic_Complex_Types and Ada.Numerics.Generic_Complex_Elementary_Functions
packages are generic versions that you can instantiate for custom or pre-defined floating-
point types. For example:
with Ada.Numerics.Generic_Complex_Types;
with Ada.Numerics.Generic_Complex_Elementary_Functions;
with Ada.Text_IO.Complex_IO;
procedure Show_Elem_Math is
X, Y : Complex;
R, Th : Float;
Listing 5: show_matrix.adb
1 with Ada.Text_IO; use Ada.Text_IO;
2
3 with Ada.Numerics.Real_Arrays;
4 use Ada.Numerics.Real_Arrays;
5
6 procedure Show_Matrix is
7
31 M1 : Real_Matrix :=
32 ((1.0, 5.0, 1.0),
33 (2.0, 2.0, 1.0));
34 M2 : Real_Matrix :=
35 ((31.0, 11.0, 10.0),
36 (34.0, 16.0, 11.0),
37 (32.0, 12.0, 10.0),
38 (31.0, 13.0, 10.0));
39 M3 : Real_Matrix := ((1.0, 2.0),
40 (2.0, 3.0));
41 begin
42 Put_Line ("V1");
43 Put_Vector (V1);
44 Put_Line ("V2");
45 Put_Vector (V2);
46 Put_Line ("V1 * V2 =");
47 Put_Line (" "
48 & Float'Image (V1 * V2));
49 Put_Line ("V1 * V2 =");
50 Put_Matrix (V1 * V2);
51 New_Line;
52
53 Put_Line ("M1");
54 Put_Matrix (M1);
55 Put_Line ("M2");
56 Put_Matrix (M2);
57 Put_Line ("M2 * Transpose(M1) =");
58 Put_Matrix (M2 * Transpose (M1));
59 New_Line;
60
Project: Courses.Intro_To_Ada.Standard_Library.Show_Matrix
MD5: c9df45a742a42bd47e03fbf2d0282238
Runtime output
V1
( 1.00000E+00 3.00000E+00 )
V2
( 7.50000E+01 1.10000E+01 )
V1 * V2 =
1.08000E+02
V1 * V2 =
( 7.50000E+01 1.10000E+01 )
( 2.25000E+02 3.30000E+01 )
M1
( 1.00000E+00 5.00000E+00 1.00000E+00 )
( 2.00000E+00 2.00000E+00 1.00000E+00 )
M2
( 3.10000E+01 1.10000E+01 1.00000E+01 )
( 3.40000E+01 1.60000E+01 1.10000E+01 )
( 3.20000E+01 1.20000E+01 1.00000E+01 )
( 3.10000E+01 1.30000E+01 1.00000E+01 )
M2 * Transpose(M1) =
( 9.60000E+01 9.40000E+01 )
( 1.25000E+02 1.11000E+02 )
( 1.02000E+02 9.80000E+01 )
( 1.06000E+02 9.80000E+01 )
M3
( 1.00000E+00 2.00000E+00 )
( 2.00000E+00 3.00000E+00 )
Inverse (M3) =
(-3.00000E+00 2.00000E+00 )
( 2.00000E+00 -1.00000E+00 )
abs Inverse (M3) =
( 3.00000E+00 2.00000E+00 )
( 2.00000E+00 1.00000E+00 )
Determinant (M3) =
-1.00000E+00
Solve (M3, V1) =
( 3.00000E+00 -1.00000E+00 )
Eigenvalues (M3) =
(continues on next page)
Matrix dimensions are automatically determined from the aggregate used for initialization
when you don't specify them. You can, however, also use explicit ranges. For example:
M1 : Real_Matrix (1 .. 2, 1 .. 3) :=
((1.0, 5.0, 1.0),
(2.0, 2.0, 1.0));
The Ada.Numerics.Real_Arrays package implements operations for the Float type. Sim-
ilar packages are available for Long_Float and Long_Long_Float types. In addition, the
Ada.Numerics.Generic_Real_Arrays package is a generic version that you can instantiate
with custom floating-point types. For example, the Real_Arrays package can be defined
as follows:
TWENTYFOUR
APPENDICES
265
Introduction to Ada
The same examples could also contain discriminants. In this case, (<>) is replaced by a
list of discriminants, e.g.: (D: DT).
® Note
To get the correct container name, replace the whitespace by _ in the names above. (For
example, Hashed Maps becomes Hashed_Maps.)
The following table presents the prefixing applied to the container name that depends on
its version. As indicated in the table, the standard version does not have a prefix associated
with it.