Rationale for Ada 2005: Access types

RUSTOP
BACKNEXT

ENG

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

  1        type Gender is (Male, Female, Neuter);
  2        type Mutant (Sex : Gender := Neuter) is
  3                record
  4                        Birth : Date;
  5                        case Sex is
  6                                when Male   => Bearded  : Boolean;
  7                                when Female => Children : Integer;
  8                                when Neuter => null;
  9                        end case;
 10                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

  1        The_Thing : Mutant;

@ then The_Thing is neuter by default but could have its sex changed by a whole record assignment thus

  1        The_Thing := (Male, The_Thing.Birth, True);

@ It now is Male and has a beard but the date of birth retains its previous value.

@ 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

  1        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

  1        type Mutant_Name is access all Mutant;
  2        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.

  1        Some_Thing : aliased Mutant;
  2        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

  1        type Monster is
  2                record
  3                        Head : Mutant (Female);
  4                        Tail : aliased Mutant;
  5                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

  1        M : Monster;
  2        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

  1        generic
  2                type T is private;
  3                Before, After : T;
  4                type Name is access all T;
  5                A_Name : in out Name;
  6        procedure Sex_Change;
  7                procedure Sex_Change is
  8                type Single is array (1..1) of aliased T;
  9                X : Single := (1 => Before);
 10        begin
 11                A_Name := X (1)'Access;
 12                X := (1 => After);
 13        end Sex_Change;

@ and then

  1        A_Neuter : Mutant_Name (Neuter);  -- fixed neuter
  2        procedure Surgery is new Sex_Change(
  3                T      => Mutant,
  4                Before => (Sex => Neuter),
  5                After  => (Sex => Male, Bearded, True),
  6                Name   => Mutant_Name,
  7                A_Name => A_Neuter);
  8        Surgery;   -- call of Surgery makes A_Neuter hairy

@ The problem here is that there are loopholes in the checks in the procedure Sex_Change. 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 call Surgery, 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

  1        package Beings is
  2                type Mutant is private;
  3                type Mutant_Name is access Mutant;
  4                F, M : constant Mutant;
  5        private
  6                type Mutant (Sex : Gender := Neuter) is
  7                        record
  8                                ... -- as above
  9                        end record;
 10                F : constant Mutant := (Female, ... );
 11                M : constant Mutant := (Male, ... );
 12        end Beings;

@ Now suppose some innocent user (who has not peeked at the private part) writes

  1        Chris : Mutant_Name := new Mutant'(F); -- OK
  2        ...
  3        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. In the end the approach has been taken of getting to the root of the matter in Ada 2005 and disallowing access subtypes for general access types that have defaults for their discriminants. So both the explicit Things_Name and also Mutant_Name(Neuter) are forbidden in Ada 2005.

@ Moreover we cannot even have an access type such as Mutant_Name when the access type completes a private view that has no discriminants.

@ By removing these nasty access subtypes it is now possible to say that heap objects are no longer considered constrained in this situation.

@ The other change in this area concerns type conversions. A variation on the gender theme is illustrated by the following

  1        type Gender is (Male, Female);
  2        type Person (Sex : Gender) is
  3                record
  4                        Birth : Date;
  5                        case Sex is
  6                                when Male   => Bearded  : Boolean;
  7                                when Female => Children : Integer;
  8                        end case;
  9                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

  1        type Person_Name is access all Person;
  2        type Mans_Name is access all Person (Male);
  3        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

  1        It  : Person_Name := Chris'Access;
  2        Him : Mans_Name   := Jack'Access;
  3        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

  1        Her := Womans_Name (It);

@ But curiously enough this is not permitted in Ada 95 although the reverse conversion

  1        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 [2]. The poor student was invited to solve an impossible problem. But they will be successful in Ada 2005.

Rationale for Ada 2005: Access types

@ENGRUSTOPBACKNEXT

5. Ссылочные типы и дискриминанты

@ Этот заключительный раздел касается двух вещей. Первое - об ссылочных компонентах дискриминантных типов, которые могли бы исчезнуть или измениться загадочно, и второе - о преобразованиях типов.

@ Напомним, что мы можем иметь вариантную запись (рекорд) типа:

  1        type Gender is (Male, Female, Neuter);
  2        type Mutant (Sex : Gender := Neuter) is
  3                record
  4                        Birth : Date;
  5                        case Sex is
  6                                when Male   => Bearded  : Boolean;
  7                                when Female => Children : Integer;
  8                                when Neuter => null;
  9                        end case;
 10                end record;

@ Она представляет мир, в котором есть три пола, мужчины, которые могут иметь бороды, женщины, которые могут иметь детей, и безполых (neuters), которые являются довольно бесполезными. Отметим значение по умолчанию для дискриминанта.

@ Это означает это, если мы объявляем беспрепятственный (unconstarined) объект таким образом:

  1        The_Thing : Mutant;

@ тогда The_Thing является безполым по умолчанию, но можно изменить его секс целым рекордным назначением таким образом:

  1        The_Thing := (Male, The_Thing.Birth, True);

@ И теперь запись становится Male-вариантом и имеет бороду, но дата рождения сохраняет ее предыдущее значение.

@ Проблема с этим видом объектов состоит в том, что их компоненты могут исчезнуть. Если запись будет изменена на Female-вариант то борода исчезнет и будет заменена детьми. Из-за этого призрачного поведения запрещаются определенные операции на изменчивых объектах.

@ Одно очевидное правило состоит в том, что не допустимо переименовать компоненты, которые могут исчезнуть. Так:

  1        Hairy : Boolean renames The_Thing.Bearded; -- illegal

@ по правилам Ады 83 это не разрешается. И эти правила были водонепроницаемы. Однако, Ада 95 добавила некоторые возможности. Объекты и компоненты теперь могли быть помечены как aliased, и признак Access мог быть применен. Дополнительные правила были тогда добавлены, чтобы предотвратить создание ссылок на вещи, которые могли исчезнуть.

@ Однако, было обнаружено, что правила ada относительно ссылочных типов всё ещё оставались водонепроницаемы.

@ Были предприняты попытки устранить эти проблемы установить их несколько постепенным способом. Эти вопросы являются тонкими и не кажутся стоящими описания в в этом общем представлении.

@ Мы ограничимся только несколькими примерами.

@ В Аде 95 мы можем объявить типы:

  1        type Mutant_Name is access all Mutant;
  2        type Things_Name is access all Mutant (Neuter);

@ Естественно объекту типа Things_Name разрешается ссыслаться только на объект типа Mutant, Sex которого является Neuter.

  1        Some_Thing : aliased Mutant;
  2        Thing_Ptr  : Things_Name := Some_Thing'Access;

@ Ситуация вышла бы из под контроля, если бы мы позволили Some_Thing иметь возможность менять сексуальную ориентацию. Соответствующее правило ada гласит, что aliased объект типа Some_Thing ограничен. И это спасает дело.

@ Однако, ситуация ухудьшается, когда тип, такой как Mutant используется для компонента другого типа следующим образом:

  1        type Monster is
  2                record
  3                        Head : Mutant (Female);
  4                        Tail : aliased Mutant;
  5                end record;

@ Здесь мы пытаемся объявить кошмарного монстра, голова которого - female, а хвост deceivingly изменчивый. Люди с приличным образованием могли бы найти, что это напоминает им о Сиренах, которые соблазнили Одисея своими красивыми голосами в его поездке мимо монстра Сцилла и водоворота Харибда. Люди с неприличным образованием могут сравнить это с лошадью театра пантомимы (вот уж действительно, кошмар). Мы могли бы тогда написать:

  1        M : Monster;
  2        Thing_Ptr := Monster.Tail'Access;

@ Однако, правило ada гласит, что Tail должен быть ограничен, так как он - aliased, и таким образом, тип Monster недопустим. Пока неплохо.

@ Но теперь рассмотрим следующий очень противный пример:

  1        generic
  2                type T is private;
  3                Before, After : T;
  4                type Name is access all T;
  5                A_Name : in out Name;
  6        procedure Sex_Change;
  7                procedure Sex_Change is
  8                type Single is array (1..1) of aliased T;
  9                X : Single := (1 => Before);
 10        begin
 11                A_Name := X (1)'Access;
 12                X := (1 => After);
 13        end Sex_Change;

@ и тогда:

  1        A_Neuter : Mutant_Name (Neuter);  -- fixed neuter
  2        procedure Surgery is new Sex_Change(
  3                T      => Mutant,
  4                Before => (Sex => Neuter),
  5                After  => (Sex => Male, Bearded, True),
  6                Name   => Mutant_Name,
  7                A_Name => A_Neuter);
  8        Surgery;   -- call of Surgery makes A_Neuter hairy

@ Проблема состоит в том, что здесь могут твориться тёмные дела при регистрации процедуры Sex_Change. Объекту A_Name присваивается ссылка на первый компонент массива X, чье значение - Before. При этом выполняется проверка, что компонент массива имеет правильный подтип. Однако, последующее присваивание на целый массив изменяет значение компонента на After, и это может изменить подтип X (1) и при этом нет никакой проверки относительно A_Name. Ключевой пункт здесь в том, что generic не знает, что тип T изменчив; эта информация не является частью родового контракта.

@ Так, когда мы вызываем Surgery, объект A_Neuter внезапно находит, что вырастил бороду! Подобная трудность происходит, когда частные типы вовлечены, потому что частичное представление и полное представление могли бы не согласиться о том, ограничен ли тип или нет. Рассмотрим:

  1        package Beings is
  2                type Mutant is private;
  3                type Mutant_Name is access Mutant;
  4                F, M : constant Mutant;
  5        private
  6                type Mutant (Sex : Gender := Neuter) is
  7                        record
  8                                ... -- as above
  9                        end record;
 10                F : constant Mutant := (Female, ... );
 11                M : constant Mutant := (Male, ... );
 12        end Beings;

@ Теперь предположим, некоторый невинный пользователь (который не посмотрел на частную часть) напишет:

  1        Chris : Mutant_Name := new Mutant'(F); -- OK
  2        ...
  3        Chris.all := M; -- raises Constraint_Error

@ Это очень удивительно. Пользователь не знает, что тип Mutant изменчив и в особенности не может видеть, что М и F отличны в некотором роде. С внешней стороны они только похожи на константы одного итого же типа. Большая неприятность состоит в том, что есть правило в Аде 95, которое говорит, что объект, созданный программой распределения ограничен. Таким образом, новый объект, именуемый Chris является постоянно Female, и поэтому попытка назначать значение М с его Bearded компонентом к ней обречена.

@ Попытки увязать эти и связанные проблемы с множеством минимальных правил казались обреченными. В конце концов, был введён запрет на использование ссылочных подтипов и для общих ссылочных типов, которые имеют значения по умолчанию для дискриминантов. Так Things_Name, а так также Mutant_Name (Neuter) запрещаются в Аде 2005.

@ Кроме того, мы даже не можем иметь ссылочный тип Mutant_Name, когда ссылочный тип заканчивает частное представление, которое не имеет никаких дискриминантов.

@ Запрещаяя эти противные ссылочные подтипы теперь мы можем сказать, что объекты кучи больше не считаются ограниченными в этой ситуации.

@ Другое изменение в этой области касается преобразований типов. Разновидность гендерной темы иллюстрируется следующим образом:

  1        type Gender is (Male, Female);
  2        type Person (Sex : Gender) is
  3                record
  4                        Birth : Date;
  5                        case Sex is
  6                                when Male   => Bearded  : Boolean;
  7                                when Female => Children : Integer;
  8                        end case;
  9                end record;

@ Отметим, что этот тип не изменчив, таким образом все люди увязываются с их сексом с рождения.

@ Мы можем теперь объявить некоторые ссылочные типы:

  1        type Person_Name is access all Person;
  2        type Mans_Name is access all Person (Male);
  3        type Womans_Name is access all Person (Female);

@ теперь мы можем управлять различными именами людей. Мы естественно использовали бы Person_Name, если бы мы не знали секс человека, а если бы знали то использовали бы Mans_Name или Womans_Name соответственно.

@ Мы могли бы иметь:

  1        It  : Person_Name := Chris'Access;
  2        Him : Mans_Name   := Jack'Access;
  3        Her : Womans_Name := Jill'Access;

@ Если мы позже обнаруживаем, что Chris - фактически Christine тогда, мы могли бы любить заменить значение такое как It к более соответствующей переменной типа Неr. Таким образом, мы хотели бы написать:

  1        Her := Womans_Name (It);

@ Но любопытно достаточно это не разрешается в Аде 95 хотя обратная конверсия:

  1        It := Person_Name (Her);

@ допустима. Правило ada состоит в том, что любые ограничения должны статически соответствовать или преобразование должно быть к беспрепятственного типа. По-видимому причина состояла в том, чтобы избежать проверок во время выполнения. Но эта нехватка симметрии неприятна, и это правило было изменено в Аде 2005, чтобы позволить преобразование в обоих направлениях с проверкой во время выполнения по мере необходимости.

@ Вышеупомянутый пример - это Упражнение 19.8 (1) из учебника [2]. Бедных студентов заставляли решать невозможную проблему. Но на Аде 2005 это имеет решение.

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:54

www.1tzg.ru . .