Rationale for Ada 2012
2.2 Aspect specifications
Although in a sense the introduction of aspect specifications
is incidental to the main themes of Ada 2012 which are contracts, real-time,
and containers, the clarity (and some might say upheaval) brought by
aspect specifications merits their description first.
An early proposal to
introduce preconditions was by the use of pragmas. Thus to give a precondition
not Is_Full to the usual Push
procedure acting on a stack S and a corresponding
postcondition not Is_Empty, it was
proposed that this should be written as
pragma Precondition(Push, not Is_Full(S));
pragma Postcondition(Push, not Is_Empty(S));
But this looks ugly and is verbose since it mentions
Push in both pragmas. Moreover, potential
problems with overloading means that it has to be clarified to which
procedure Push they apply if there happen
to be several. As a consequence it was decreed that the pragmas had to
apply to the immediately preceding subprogram. Which of course is not
the case with pragma Inline which with overloading
applies to all subprograms with the given name. Other curiosities include
the need to refer to the formal parameters of Push
(such as S) so that the expression has to
be resolved taking heed of these even though it is detached from the
actual specification of Push.
Other pragmas proposed were Inherited_Precondition
and Inherited_Postcondition for use with dispatching
subprograms.
So it was a mess and an alternative was sought. The
solution which evolved was to get away from wretched pragmas in such
circumstances. Indeed, the Ada 83 Rationale
[6]
says "In addition, a program text can include elements that have
no influence on the meaning of the program but are included as information
and guidance for the human reader or for the compiler. These are: Comments;
Pragmas..."
So pragmas were meant to have no effect on the meaning
of the program. Typical pragmas in Ada 83 were List,
Inline, Optimize,
and Suppress. But in later versions of Ada,
pragmas are used for all sorts of things. The days when pragmas had no
effect are long gone!
The basic need was
to tie the pre- and postconditions syntactically to the specification
of
Push so that there could be no doubt as
to which subprogram they applied; this would also remove the need to
mention the name of the subprogram again. And so, as described in the
Introduction (see Section
1.3.1) we now
have
procedure Push(S: in out Stack; X: in Item)
with
Pre => not Is_Full(S),
Post => not Is_Empty(S);
The syntax for aspect specification is
aspect_specification ::=
with aspect_mark [ => expression] { ,
aspect_mark [ => expression] }
and this can be used with a variety of structures,
subprogram declaration being the example here.
Note especially the
use of the reserved word with. Serious attempts were made to think
of another word so as to avoid using with again but nothing better
was suggested.
It might be thought that it would be confusing to
use with which is firmly associated with context clauses. However,
recall that with has also been used to introduce generic formal
subprogram parameters without causing confusion since 1983. Thus
generic
with function This ...
procedure That ...
Moreover, Ada 95 introduced
the use of with for type extension as in
type Circle is new Object with
record
Radius: Float;
end record;
So in Ada 95 there were already many distinct uses
of with and another one will surely do no harm. It's a versatile
little word.
Any risk of confusion is easily avoided by using
a sensible layout. Thus a with clause should start on a new line
at the left and aligned with the following unit to which it applies.
A formal generic parameter starting with with should be aligned
with other formal parameters and indented after the word generic. In
the case of type extension, with should be at the end of the line.
Finally, in the case of aspect specifications, with should be
at the beginning of a line and indented after the entity to which it
applies.
Having introduced aspect specifications which are
generally so much nicer than pragmas, it was decided to allow aspect
specifications for all those situations where pragmas are used and an
aspect specification makes sense (typically where is applies to an entity
rather than a region of text). And then to make most of the pragmas obsolete.
Before looking at the old pragmas concerned in detail,
two general points are worth noting.
The usual linear elaboration
rules do not apply to the expression in an aspect specification. It is
essentially sorted out at the freezing point of the entity to which the
aspect applies. The reason for this was illustrated by an example in
the Introduction which was
type Stack is private
with
Type_Invariant => Is_Unduplicated(Stack);
The problem here is that the function Is_Unduplicated
cannot be declared before that of the type Stack
and yet it is needed in the aspect specification of the declaration of
Stack. So there is a circularity which is
broken by saying that the elaboration of aspect specifications is deferred.
The other general point
is that some aspects essentially take a Boolean value. For example the
pragma Inline is replaced by the aspect Inline
so that rather than writing
procedure Do_It( ... );
pragma Inline(Do_It);
we now write
procedure Do_It( ... )
with Inline;
The aspect Inline
has type Boolean and so we could write
procedure Do_It( ... )
with Inline => True;
To have insisted on this would have been both pedantic
and tedious and so in the case of a Boolean aspect there is a rule that
says that
=> True can be omitted and
True
is then taken by default. But this does not apply to
Default_Value
and
Default_Component_Value as explained later
in Section
2.6 on default initial values.
Note however that omitting
the whole aspect by just writing
procedure Do_It( ... );
results of course in the Inline
aspect of Do_It being False.
A mad programmer could
even use defaults for preconditions and postconditions. Thus writing
procedure Curious( ... )
with Pre;
in which by default the precondition is taken to
be True, results in the Curious
procedure always being callable.
We will now consider the fate of the various pragmas
in Ada 2005. Some are replaced by aspect specifications and the pragmas
made obsolete (of course, they can still be used, but should be discouraged
in new programs). Some are paralleled by aspect specifications and the
user left with the choice. Some are unchanged since for various reasons
aspect specifications were inappropriate. Some pragmas are new to Ada
2012 and born obsolete.
The following are the obsolete pragmas with some
examples of corresponding aspect specifications.
The pragmas
Inline,
No_Return, and
Pack
are examples having
Boolean aspects. We can
now write
procedure Do_It( ... )
with Inline;
procedure Fail( ... )
with No_Return;
type T is ...
with Pack;
Some thought was given as to whether the name of
the Pack aspect should be Packing
rather than Pack because this gave better
resonance in English. But the possible confusion in having a different
name to that of the pragma overrode the thought of niceties of (human)
language.
Curiously enough the old pragmas Inline
and No_Return could take several subprograms
as arguments but naturally the aspect specification is explicitly given
to each one.
If several aspects
are given to a procedure then we simply put them together thus
procedure Kill
with Inline, No_Return;
rather than having to supply several pragmas (which
careless program maintenance might have scattered around).
In the case of a procedure
without a distinct specification, the aspect specification goes in the
procedure body before is thus
procedure Do_It( ... )
with Inline is ...
begin
...
end Do_It;
This arrangement is
because the aspect specification is very much part of the specification
of the subprogram. This will be familiar to users of SPARK where we might
have
procedure Do_It( ... )
--# global in out Stuff;
is ...
If a subprogram has a distinct specification then
we cannot give a language-defined aspect specification on the body; this
avoids problems of conformance. If there is a stub but no specification
then any aspect specification goes on the stub but not the body. Thus
aspect specifications go on the first of specification, stub, and body
but are never repeated. Note also that we can give aspect specifications
on other forms of stubs and bodies such as package bodies, task bodies
and entry bodies but none are defined by the language.
In the case of a stub,
abstract subprogram, and null subprogram which never have bodies, the
aspect specification goes after is separate, is abstract
or is null thus
procedure Action(D: in Data) is separate
with Convention => C;
procedure Enqueue( ... ) is abstract
with Synchronization => By_Entry;
procedure Nothing is null
with Something;
The above example of the use of Synchronization
is from the package Synchronized_Queue_Interfaces,
a new child of Ada.Containers as mentioned
in the Introduction.
The same style is followed
by the newly introduced expression functions thus
function Inc (A: Integer) return Integer is (A + 1)
with Inline;
Other examples of
Boolean
aspects are
Atomic,
Volatile,
and
Independent. We now write for example
Converged: Boolean := False
with Atomic;
The aspects
Atomic_Components,
Volatile_Components and
Independent_Components
are similar.
The three pragmas
Convention,
Import and
Export
are replaced by five aspects, namely
Import,
Export,
Convention,
External_Name and
Link_Name.
For example, rather
than, (see
[7] page 702)
type Response is access procedure (D: in Data);
pragma Convention(C, Response);
procedure Set_Click(P: in Response);
pragma Import(C, Set_Click);
procedure Action(D: in Data) is separate;
pragma Convention(C, Action);
we now more neatly
write
type Response is access procedure (D: in Data)
with Convention => C;
procedure Set_Click(P: in Response)
with Import, Convention => C;
procedure Action(D: in Data) is separate
with Convention => C;
Note that the aspects
can be given in any order whereas in the case of pragmas, the parameters
had to be in a particular order. We could have written
with Import
=> True but that would have been pedantic. As another example
(see the
RM
7.4), instead of
CPU_Identifier: constant String(1 .. 8);
pragma Import(Assembler, CPU_Identifier, Link_Name => "CPU_ID");
we now have
CPU_Identifier: constant String(1 .. 8)
with Import, Convention => Assembler, Link_Name => "CPU_ID";
Observe that we always have to give the aspect name
such as Convention whereas with pragmas Import
and Export, the parameter name Convention
was optional. Clearly it is better to have to give the name.
The pragma
Controlled
which it may be recalled told the system to keep its filthy garbage collector
off my nice access type is plain obsolete and essentially abandoned.
It is doubted whether it was ever used. The subclause of the
RM
(13.11.3) relating to this pragma is now used by a new pragma
Default_Storage_Pools
which will be discussed in Section
6.4 on
access types and storage pools.
The pragma
Unchecked_Union
is another example of a pragma replaced by a
Boolean
aspect. So we now write
type Number(Kind: Precision) is
record
...
end record
with Unchecked_Union;
Many obsolete pragmas
apply to tasks. The aspect
Storage_Size takes
an expression of any integer type. Thus in the case of a task type without
a task definition part (and thus without
is and matching
end)
we write
task type T
with Storage_Size => 1000;
In the case of a task
type with entries we write
task type T
with Storage_Size => 1000 is
entry E ...
...
end T;
The interrupt pragmas
Attach_Handler and
Interrupt_Handler
now become
procedure P( ... )
with Interrupt_Handler;
which specifies that
the protected procedure P can be a handler
and
procedure P( ... )
with Attach_Handler => Some_Id;
which actually attaches P
to the interrupt Some_Id.
The pragmas
Priority
and
Interrupt_Priority are replaced by corresponding
aspect specifications for example
task T
with Interrupt_Priority => 31;
protected Object
with Priority => 20 is
-- ceiling priority
Note that a protected type or singleton protected
object always has is and the aspect specification goes before
it.
Similarly, instead
of using the pragma
Relative_Deadline we can
write
task T
with Relative_Deadline => RD;
The final existing pragma that is now obsolete is
the pragma
Asynchronous used in the Distributed
Systems Annex and which can be applied to a remote procedure or remote
access type. It is replaced by the
Boolean
aspect
Asynchronous.
That covers all the existing Ada 2005 pragmas that
are now obsolete.
Two new pragmas in Ada 2012 are
CPU
and
Dispatching_Domain but these are born
obsolete. Thus we can write either of
task My Task is
pragma CPU(10);
or
task My_Task
with CPU => 10 is
and similarly
task Your_Task is
pragma Dispatching_Domain(Your_Domain);
or
task Your_Task
with Dispatching_Domain => Your_Domain is
The reason for introducing
these pragmas is so that existing tasking programs with copious use of
pragmas such as Priority can use the new facilities
in a similar style. It was considered inelegant to write
task My_Task
with CPU => 10 is
pragma Priority(5);
and a burden to have
to change programs to
task My_Task
with CPU => 10, Priority => 5 is
So existing programs,
can be updated to
task My_Task is
pragma CPU(10);
pragma Priority(5);
(One other pragma that was never born at all was
Implemented which eventually turned into the
aspect Synchronization often used to ensure
that an abstract procedure is actually implemented by an entry as illustrated
earlier.)
A number of existing pragmas are paralleled by aspect
specifications but the pragmas are not made obsolete. Examples are the
pragmas relating to packages such as
Pure,
Preelaborate,
Elaborate_Body
and so on.
Thus we can write either
of
package P is
pragma Pure(P);
end P;
or
package P
with Pure is
end P;
The author prefers the former but some avant garde
programmers might like to use the latter.
Note that
Preelaborable_Initialization
is unusual in that it cannot be written as an aspect specification for
reasons that need not bother us. The inquisitive reader can refer to
AI-229
for the details.
Finally, there are many pragmas that do not relate
to any particular entity and so for which an aspect specification would
be impossible.
These include
Assert and
Assertion_Policy,
Suppress
and
Unsuppress,
Page
and
List,
Optimize
and
Restrictions.
As well as replacing pragmas, aspect specifications
can be used instead of atrribute definition clauses.
For example rather
than
type Byte is range 0 .. 255;
followed (perhaps much
later) by
for Byte'Size use 8;
we can now write
type Byte is range 0 .. 255
with Size => 8;
Similarly
type My_Float is digits 20
with Alignment => 16;
Loose_Bits: array (1 .. 10) of Boolean
with Component_Size => 4;
type Cell_Ptr is access Cell
with Storage_Size => 500 * Cell'Size / Storage_Unit,
Storage_Pool => Cell_Ptr_Pool;
S: Status
with Address => 8#100#;
type T is delta 0.1 range –1.0 .. +1.0
with Small => 0.1;
But we cannot use this
technique to replace an enumeration representation clause or record representation
clause. Thus although we can write
type RR is
record
Code: Opcode;
R1: Register;
R2: Register;
end record
with Alignment => 2, Bit_Order => High_Order_First;
the layout information
has to be done by writing
for RR use
record
Code at 0 range 0 .. 7;
R1 at 1 range 0 .. 3;
R2 at 1 range 4 .. 7;
end record;
It is interesting to
note that attribute definition clauses were not made redundant in the
way that many pragmas were made redundant. This is because there are
things that one can do with attribute definition clauses that cannot
be done with aspect specifications. For example a visible type can be
declared in a visible part and then details of its representation can
be given in a private part. Thus we might have
package P is
type T is ...
private
Secret_Size: constant := 16;
for T'Size use Secret_Size;
end P;
It's not that convincing because the user can use
the attribute T'Size to find the Secret_Size
anyway. But some existing programs are structured like that and hence
the facility could hardly be made redundant.
The examples above have shown aspect specifications
with the following constructions: subprogram declaration, subprogram
body, stub, abstract subprogram declaration, null procedure declaration,
full type declaration, private type declaration, object declaration,
package declaration, task type declaration, single task declaration,
and single protected declaration. In addition they can be used with subtype
declaration, component declaration, private extension declaration, renaming
declaration, protected type declaration, entry declaration, exception
declaration, generic declaration, generic instantiation, and generic
formal parameter declaration.
The appropriate layout should be obvious. In the
case of a large structure such as a package specification and any body,
the aspect specification goes before is. But when something is
small and all in one piece such as a procedure specification, stub, null
procedure, object declaration or generic instantiation any aspect specification
goes at the end of the declaration; it is then more visible and less
likely to interfere with the layout of the rest of the structure.
In some cases such as exception declarations there
are no language defined aspects that apply but implementations might
define their own aspects.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: