Rationale for Ada 2005

John Barnes
Contents   Index   References   Search   Previous   Next 

3.5 Access types and discriminants

This final topic concerns two matters. The first is about accessing components of discriminated types that might vanish or change mysteriously and the second is about type conversions.
Recall that we can have a mutable variant record such as 
type Gender is (Male, Female, Neuter);
type Mutant(Sex: Gender := Neuter) is
   record
      case Sex is
         when Male =>
            Bearded: Boolean;
         when Female =>
            Children: Integer;
         when Neuter =>
            null;
      end case;
   end record;
This represents a world in which there are three sexes, males which can have beards, females which can bear children, and neuters which are fairly useless. Note the default value for the discriminant. This means that if we declare an unconstrained object thus 
The_Thing: Mutant;
then The_Thing is neuter by default but could have its sex changed by a whole record assignment thus 
The_Thing := (Sex => Male, Bearded => True);
It now is Male and has a beard.
The problem with this sort of object is that components can disappear. If it were changed to be Female then the beard would vanish and be replaced by children. Because of this ghostly behaviour certain operations on mutable objects are forbidden.
One obvious rule is that it is not permissible to rename components which might vanish. So
Hairy: Boolean renames The_Thing.Bearded;      -- illegal
is not permitted. This was an Ada 83 rule. It was probably the case that the rules were watertight in Ada 83. However, Ada 95 introduced many more possibilities. Objects and components could be marked as aliased and the Access attribute could be applied. Additional rules were then added to prevent creating references to things that could vanish.
However, it was then discovered that the rules in Ada 95 regarding access types were not watertight. Accordingly various attempts were made to fix them in a somewhat piecemeal fashion. The problems are subtle and do not seem worth describing in their entirety in this general presentation. We will content ourselves with just a couple of examples.
In Ada 95 we can declare types such as 
type Mutant_Name is access all Mutant;
type Things_Name is access all Mutant(Neuter);
Naturally enough an object of type Things_Name can only be permitted to reference a Mutant whose Sex is Neuter.
Some_Thing: aliased Mutant;
Thing_Ptr: Things_Name := Some_Thing'Access;
Things would now go wrong if we allowed Some_Thing to have a sex change. Accordingly there is a rule in Ada 95 that says that an aliased object such as Some_Thing is considered to be constrained. So that is quite safe.
However, matters get more difficult when a type such as Mutant is used for a component of another type such as 
type Monster is
   record
      Head: Mutant(Female);
      Tail: aliased Mutant;
   end record;
Here we are attempting to declare a nightmare monster whose head is a female but whose tail is deceivingly mutable. Those with a decent education might find that this reminds them of the Sirens who tempted Odysseus by their beautiful voices on his trip past the monster Scylla and the whirlpool Charybdis. Those with an indecent education can compare it to a pantomime theatre horse (or mare, maybe indeed a nightmare). We could then write 
M: Monster;
Thing_Ptr := Monster.Tail'Access;
However, there is an Ada 95 rule that says that the Tail has to be constrained since it is aliased so the type Monster is not allowed. So far so good.
But now consider the following very nasty example 
generic
   type T is private;
   Before, After: T;
   type Name is access all T;
   A_Name: in out Name;
package Sex_Change is end;
package body Sex_Change is
   type Single is array (1..1) of aliased T;
   X: Single := (1 => Before);
begin
   A_Name := X(1)'Access;
   X := (1 => After);
end Sex_Change;
and then 
A_Neuter: Mutant_Name(Neuter);    -- fixed neuter
package Surgery is new Sex_Change(
           T => Mutant,
           Before => (Sex => Neuter),
           After => (Sex => Male, Bearded => True),
           Name => Mutant_Name,
           A_Name => A_Neuter);
                                                    -- instantiation of Surgery makes A_Neuter hairy
The problem here is that there are loopholes in the checks when the package Sex_Change is elaborated. The object A_Name is assigned an access to the single component of the array X whose value is Before. When this is done there is a check that the component of the array has the correct subtype. However the subsequent assignment to the whole array changes the value of the component to After and this can change the subtype of X(1) surreptitiously and there is no check concerning A_Name. The key point is that the generic doesn't know that the type T is mutable; this information is not part of the generic contract.
So when we instantiate Surgery (at the same level as the type Mutant_Name so that accessibility succeeds), the object A_Neuter suddenly finds that it has grown a beard!
A similar difficulty occurs when private types are involved because the partial view and full view might disagree about whether the type is constrained or not. Consider
package Beings is
   type Mutant is private;
   type Mutant_Name is access Mutant;
   F, M: constant Mutant;
private
   type Mutant(Sex: Gender := Neuter) is
      record
         ...     -- as above
      end record;
   F: constant Mutant := (Female, ... );
   M: constant Mutant := (Male, ... );
end Beings;
Now suppose some innocent user (who has not peeked at the private part) writes 
Chris: Mutant_Name := new Mutant'(F);    -- OK
...
Chris.all := M;    --raises Constraint_Error
This is very surprising. The user cannot see that the type Mutant is mutable and in particular cannot see that M and F are different in some way. From the outside they just look like constants of the same type. The big trouble is that there is a rule in Ada 95 that says that an object created by an allocator is constrained. So the new object referred to by Chris is permanently Female and therefore the attempt to assign the value of M with its Bearded component to her is doomed.
Attempting to fix these and related problems with a number of minimal rules seemed fated not to succeed. So a different approach has been taken. Rather than saying that aliased and allocated objects are always treated as constrained so that accessed components do not disappear, Ada 2005 takes the approach of preventing the Access attribute from being applied in certain circumstances by disallowing certain access subtypes at all. In particular, general access subtypes which refer to types with defaults for their discriminants are forbidden.
The net outcome is that the declaration of A_Neuter is illegal because we cannot write Mutant_Name(Neuter) and so the Surgery cannot be applied to constrained mutants. On the other hand, Chris is allowed to change sex because the allocated objects are no longer automatically constrained in the case of private types whose partial view does not have discriminants.
These changes introduce some minor incompatibilities which are explained with further examples in the Epilogue.
The other change in this area concerns type conversions. A variation on the gender theme is illustrated by the following 
type Gender is (Male, Female);
type Person(Sex: Gender) is
   record
      case Sex is
         when Male =>
            Bearded: Boolean;
         when Female =>
            Children: Integer;
      end case;
   end record;
Note that this type is not mutable so all persons are stuck with their sex from birth.
We might now declare some access types 
type Person_Name is access all Person;
type Mans_Name is access all Person(Male);
type Womans_Name is access all Person(Female);
so that we can manipulate various names of people. We would naturally use Person_Name if we did not know the sex of the person and otherwise use Mans_Name or Womans_Name as appropriate. We might have
It: Person_Name := Chris'Access;
Him: Mans_Name := Jack'Access;
Her: Womans_Name := Jill'Access;
If we later discover that Chris is actually Christine then we might like to assign the value in It to a more appropriate variable such as Her. So we would like to write 
Her := Womans_Name(It);
But curiously enough this is not permitted in Ada 95 although the reverse conversion 
It := Person_Name(Her);
is permitted. The Ada 95 rule is that any constraints have to statically match or the conversion has to be to an unconstrained type. Presumably the reason was to avoid checks at run time. But this lack of symmetry is unpleasant and the rule has been changed in Ada 2005 to allow conversion in both directions with a run time check as necessary.
The above example is actually Exercise 19.8(1) in the textbook [6]. The poor student was invited to solve an impossible problem. But they will be successful in Ada 2005.

Contents   Index   References   Search   Previous   Next 
© 2005, 2006, 2007 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe