Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

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 andLink_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.

Contents   Index   References   Search   Previous   Next 
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association:

    ARA
  AdaCore:


    AdaCore
and   Ada-Europe:

Ada-Europe