Copyright (C) А.Гавва V-0.4w май 2004

6. Трудности и рекомендации

6.1 "Сюрпризы" численных типов

Достаточно часто распространены случаи, когда программист, который только начинает знакомиться с языком программирования Ада, попадается в ловушку численных типов. Рассмотрим следующий пример программы:

    
    
    with  Ada.Text_IO;
    
    procedure Numeric is
    
        type  My_Integer_Type is new Integer range 1..5;
        
        My_Integer_Variable       : My_Integer_Type := 2;
        Standard_Integer_Variable : Integer := 2;
        Result_Variable           : Integer;
    
    begin
        Result_Variable := Standard_Integer_Variable * My_Integer_Variable;
    
        Ada.Text_IO.Put_Line
           ("Result_Variable = " &
            Result_Variable'Img);
    end Numeric;
    

Хотя на первый взгляд все выглядит достаточно логично, данная программа компилироваться не будет. Причиной ошибки будет тип My_Integer_Type, или точнее - отсутствие описания знака операции умножения "*", который одновременно воспринимает левый операнд, стандартного целочисленного типа Integer, и правый операнд, целочисленного типа My_Integer_Type. Такое поведение компилятора Ады продиктовано требованиями строгой именной эквивалентности типов.

Чтобы решить возникшую проблему, необходимо описать тип My_Integer_Type в самостоятельном пакете и описать соответствующие знаки операций, которые будут допустимы для использования над значениями данного целочисленного типа. Вполне естественно, что подобный вариант решения проблемы малопривлекателен, а подобная строгость типизации может показаться чрезмерной даже приверженцам языка Паскаль.

Следует заметить, что существует альтернативный вариант, который гораздо проще и удобнее. Следует описать My_Integer_Type не как тип, производный от типа Integer, а как подтип типа Integer с ограниченным диапазоном значений. После таких изменений наш пример программы будет иметь слудующий вид:

    
    
    with  Ada.Text_IO;
    
    procedure Numeric is
    
        subtype  My_Integer_Type is Integer range 1..5;
        
        My_Integer_Variable       : My_Integer_Type := 2;
        Standard_Integer_Variable : Integer := 2;
        Result_Variable           : Integer;
    
    begin
        Result_Variable := Standard_Integer_Variable * My_Integer_Variable;
    
        Ada.Text_IO.Put_Line
           ("Result_Variable = " &
            Result_Variable'Img);
    end Numeric;
    

В данном случае процедура Numeric будет успешно откомпилирована компилятором.

6.2 Принудительная инициализация

В объектно-ориентированных языках программирования, которые используют классы (например язык C++), можно принудительно навязать автоматическую инициализацию для всех объектов класса, снабдив класс конструктором, который гарантированно вызывается при создании объекта. Чтобы добиться подобного эффекта в Аде, необходимо при описании объекта указать присваивание начального значения, которое осуществляется в результате вызова функции. Следует заметить, что в данном случае мы не рассматриваем средства, которые предоставляют контролируемые типы Ады. Рассмотрим следующий пример:

    
    
    package Stacks is
    
        type Stack is tagged private;
    
        function Init return Stack;
    
        -- другие операции
            . . .
    
    private
        type List is array(1..10) of Integer;
        type Stack is record
            Values : List;
            Top    : Integer range 0..10;
        end record;
    end Stacks;
    
    
    
    with Stacks; use Stacks;
    procedure Main is
    
        A : Stack := Init;
        B : Stack;
    
    begin
        null;
    end Main;
    

В этом примере объект A будет надежно инициализирован для представления пустого стека. Причем, для этого можно использовать только функции предусмотренные пакетом Stacks. Выполнить инициализацию такого объекта каким-либо другим способом - нельзя, поскольку его тип - приватный. Однако, не трудно заметить, что объект B - не инициализирован. Таким образом видно, что Ада не предъявляет никаких требований по принудительной инициализации объектов.

Чтобы в подобных случаях возложить контроль за инициализацией объектов на компилятор необходимо использовать дискриминанты. Существует возможность указать наличие дискриминанта в неполном описании типа при фактическом отсутствии дискриминанта в полном описании типа. Поскольку Ада предпочитает ограниченные (constrained) объекты (то есть те объекты, размер которых известен), то применение комбинации из дискриминанта и приватного типа послужит причиной того, что компилятор будет требовать от программиста явной инициализации всех объектов такого типа.

    
    
    package Stacks is
        type Stack(<>) is private;
        -- указание того, что Stack может иметь дискриминант...
    
        function Init return Stack;
    
        -- другие операции
            . . .
    
    private
        type List is array(1..10) of Integer;
    
        -- ...даже если вы не стремитесь иметь здесь дискриминант
        type Stack is record
            Values : List;
            Top    : Integer range 0..10;
        end record;
    end Stacks;
    
    
    with Stacks; use Stacks;
    procedure Main is
    
        A : Stack := Init;
        B : Stack;
        -- теперь это недопустимо!
        -- вы должны вызвать функцию для инициализации B
    
    begin
        null;
    end Main;
    

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

6.3 Взаимно рекурсивные типы

Бывают случаи, когда необходимо иметь два (или более) типа, которые являются взаимно рекурсивными. Такие типы содержат указатели, ссылаются друг на друга. В качестве примера рассмотрим типы: Doctor - врач и Patient - пациент. Эти типы нуждаются в указателях, которые ссылаются друг на друга. Ада потребует поместить оба этих типа в один пакет. Чтобы сохранить возможность раздельной компиляции, можно описать два базовых типа в одном пакете:

    
    
    package Doctors_And_Patients is
    
        type Doctor is abstract tagged null record;
        type Doctor_Ptr is access all Doctor'Class;
    
    
        type Patient is abstract tagged null record;
        type Patient_Ptr is access all Patient'Class;
    
    end Doctors_And_Patients;
    

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

    
    
    with Doctors_And_Patients; use Doctors_And_Patients;
    
    package Doctors is
        type Doc is new Doctor with private;
    
        -- подпрограммы
            . . .
    
    private
        type Doc is new Doctor with
            record
                Pat : Patient_Ptr;
                    . . .
    
            end record;
    end Doctors;
    
    
    
    with Doctors_And_Patients; use Doctors_And_Patients;
    
    package Patients is
    
    -- далее подобно Doctors...
        . . .
    

При описании базовых типов, в пакете Doctors_And_Patients, можно также описать операции, которые будут определять элементарную функциональность базовых типов. Дополнительные операции могут быть добавлены позднее, в описаниях производных типов (например в типе Doc).

Примечательно, что тело одного пакета может видеть спецификацию другого пакета. Таким образом, тела пакетов могут иметь следующий вид:

    
    
    with Doctors; use Doctors;
    package body Patients is
    
        -- тело пакета Patients имеет доступ к сервисам Doctors
        -- описанным в спецификации пакета Doctors
    
            . . .
    
    end Patients;
    
    
    
    with Patients; use Patients;
    package body Doctors is
    
        -- тело пакета Doctors имеет доступ к сервисам Patients
        -- описанным в спецификации пакета Patients
    
            . . .
    
    end Doctors;
    

6.4 Рекомендации по построению абстракций

Как уже говорилось, пакет Ады является инструментом абстракции данных. В этом случае пакет содержит какой-либо главный тип, и такая конструкция очень подобна классам, используемым в других объектно-ориентированных языках программирования. Чтобы избежать неоднозначностей (напомним, что понятие класса в Аде отличается от понятия класса в других объектно-ориентированных языках), мы называем такую конструкцию абстракцией. В большинстве подобных случаев, главный тип описан как приватный тип или тип, имеющий приватное расширение. Спецификация пакета также может содержать описания других типов, мы будем называть их обычными типами. Эти типы, как правило, используются для построения интерфейса (когда они описаны публично), или для представления внутренних структур данных (когда они описаны приватно).

Простой демонстрацией подобного подхода построения абстракции может служить следующий схематический пример:

    
    
    with Angle;
    package Coordinates is
    
        type Object is ...    -- главный тип абстракции
                              -- (приватный или с приватным расширением)
    
        type Geographic is    --  обычный тип (описан публично)
            record
                Latitude  : Angle.Radian;
                Longitude : Angle.Radian;
            end record;
    
    private
        . . .
    
    end Coordinates;
    

Подобный подход является базисом. При этом, программисту предоставляется широкий выбор: должен-ли он использовать тэговые типы? контролируемые типы? ...

6.4.1 Тэговые типы - не для всех абстракций!

Первое правило - очень простое: не следует использовать тэговые типы для очень простых абстракций, таких как расстояние, угол, температура, и так далее:

6.4.2 Контролируемые или не контролируемые?

Когда главный тип абстракции является производным от Ada.Finalization.Controlled, то вызовы Initialize, Adjust и Finalize автоматически выполняются компилятором (так указывается в руководстве по языку). Это полезно для предотвращения утечек памяти (компилятор никогда не забудет удалить объект, который больше не используется). Это полезно при дублировании объекта: две копии могут быть сделаны независимыми.

Однако эти свойства должны использоваться только в случаях, когда традиционное поведение компилятора, принятое по умолчанию, не удовлетворяет потребности разрабатываемой абстракции. Действительно, как только главный тип абстракции описан как контролируемый, то программист обязан заботиться о контролируемой обработке для каждого потомка. Таким образом, если потоммок добавляет какое-либо расширение, то унаследованные операции Initialize, Adjust и Finalize не имеют никакого представления о таком расширении. Следовательно, вы обязаны переопределять эти операции для потомков вашей абстракции.

6.4.3 Никогда не используйте неинициализированные объекты

Для избежения непредсказуемого поведения при использовании неинициализированных объектов, описание главного типа абстракции должно устанавливать значения по умолчанию для каждого атрибута. Следовательно, каждая абстракция должна описывать публичный объект nil, который может быть использован как значение атрибута по умолчанию, в контексте описания абстракции более высокого уровня.

Для простых типов, таких как Integer_32 или Float_6, значение по умолчанию соответствует ограниченному выбору значений, которые могут быть использованы вместо Integer_32'Last или Float_6'Last.

Для перечислимых типов значение по умолчанию должны соответствовать выбору по умолчанию.

6.4.4 Создание и удаление объектов

Если абстракция описывает функцию Create, которая возвращает экземпляр объекта главного типа абстракции, то все потомки этой абстракции будут наследовать такую операцию. Это может быть опасно в случаях, когда абстракция-потомок имеет больше атрибутов чем абстракция-родитель. Действительно, унаследованная от предка функция Create может не инициализировать дополнительные атрибуты абстракции-потомка. В концепции Ada 95 предлагаются два варианта решения для предотвращения наследования функции Create:

К сожалению, ни одно из этих решений нельзя назвать полностью удовлетворительным.

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

    
    
    A := Create (...).all;
    

Второе решение затрудняет автоматическую генерацию кода. Кроме того, оно вызывает принудительное копирование объектов после их создания (минимум однократно), что является дополнительным расходом производительности.

К счастью, в Аде реально не требуется явная подпрограмма Create. Любой экземпляр объекта абстракции создается автоматически, или при его описании (статический экземпляр), или с использованием динамического размещения:

    
    
    Instance := new Class.Object
    

Следовательно, должна быть описана только подпрограмма инициализации. Если инициализация принятая по умолчанию не удовлетворяет потребностям, то могут быть описаны новые подпрограммы инициализации (с дополнительными параметрами или без). Чтобы такие подпрограммы были не наследуемыми, они должны принимать в качестве главного параметра объект надклассового типа ('Class). Для простых абстракций, вместо Initialize, может быть использован метод Set_<<Attribute_Name>>.

Для удаления объекта абстракции, когда главный тип абстракции - контролируемый, подпрограмма Delete может быть безболезненно заменена на Finalize. В случае когда главный тип абстракции - не контролируемый, необходимость в подпрограмме Delete - отсутствует.

6.4.5 Именование тэговых типов

При построении абстракций с применением тэговых типов, необходимо использовать согласованные наименования как для тэговых типов, так и для ассоциируемых с ними пакетов. Следует заметить, что соглашения по наименованию часто являются источником "религиозных войн". В следствие этого существует два различных подхода.

Первый подход, за исключением двух специальных случаев, позволяет установить единые соглашения по наименованию описаний, вне зависимости от использования объектно-ориентированных свойств, и при этом он интегрирует в себе использование объектно-ориентированных свойств:

Следующий пример, который состоит из двух частей, демонстрирует использование этого варианта соглашений по наименованию. Для первой части этого примера предположим, что тип Set_Element описывается где-то в другом месте:

    
    
    package Abstract_Sets is
       type Set is abstract tagged private;
       -- пустое множество
       function Empty return Set is abstract;
       -- построение множества из одного элемента
       function Unit (Element: Set_Element) return Set is abstract;
       -- объединение двух множеств
       function Union (Left, Right: Set) return Set is abstract;
       -- пересечение двух множеств
       function Intersection (Left, Right: Set) return Set is abstract;
       -- удаление элемента из множества
       procedure Take (From    : in out Set;
                       Element :    out Set_Element) is abstract;
       Element_Too_Large : exception;
    
    private
       type Set is abstract tagged null record;
    end Abstract_Sets;
    
    
    with Abstract_Sets;
    package Bit_Vector_Sets is   -- одна реализация абстракции множества
       type Bit_Set is new Abstract_Sets.Set with private;
       . . .
    private
       Bit_Set_Size : constant := 64;
       type Bit_Vector is ...
       type Bit_Set is new Abstract_Sets.Set with
          record
             Data : Bit_Vector;
          end record;
    end Bit_Vector_Sets;
    
    
    with Abstract_Sets;
    package Sparse_Sets is  -- альтернативная реализация абстракции множества
       type Sparse_Set is new Abstract_Sets.Set with private;
       . . .
    private
       . . .
    end Sparse_Sets;
    
    

Вторая часть этого примера демонстрирует использование соглашений по наименованию для смешанных пакетов (mixin packages), которые обеспечивают поддержку оконной системы.

Предположим, что у нас есть тэговый лимитированный приватный тип Basic_Window, описание которого имеет следующий вид:

    
    
    type Basic_Window is tagged limited private;
    

Тогда:

    
    
    generic
       type Some_Window is abstract new Basic_Window with private;
    package Label_Mixin is
       type Window_With_Label is abstract new Some_Window with private;
       . . .
    private
       . . .
    end Label_Mixin;
    
    
    generic
       type Some_Window is abstract new Basic_Window with private;
    package Border_Mixin is
       type Window_With_Label is abstract new Some_Window with private;
       . . .
    private
       . . .
    end Border_Mixin;
    

Второй подход отображает использование объектно-ориентированных свойств с помощью специальных имен и суффиксов:

Следующий пример демонстрирует использование этого варианта соглашений по наименованию:

    
    
    package Shape is
       subtype Side_Count is range 0 .. 100;
       type Instance (Sides: Side_Count) is tagged private;
       subtype Class is Instance'Class;
       . . .
       -- операции Shape.Instance
    private
       . . .
    end Shape;
    
    
    with Shape; use Shape;
    package Line is
       type Instance is new Shape.Instance with private;
       subtype Class is Instance'Class;
       . . .
       -- переопределенные или новые операции
    private
       . . .
    end Line;
    
    
    with Shape; use Shape;
    generic
       type Origin is new Shape.Instance;
    package With_Color_Facet is
       type Instance is new Origin with private;
       subtype Class is Instance'Class;
       -- операции для colored shapes
    private
       . . .
    end With_Color_Facet;
    
    
    with Line; use Line;
    with With_Color_Facet;
    package Colored_Line is new With_Color_Facet (Line.Instance);
    

Простые описания, в этом случае, могут иметь следующий вид:

    
    
    Red_Line : Colored_Line.Instance;
    procedure Draw (What : Shape.Instance);
    

Показанная выше схема соглашений по наименованию корректно работает как в случае использования полных имен, так и при использовании спецификатора use. При использовании одного и того же имени для всех специфических типов (например type Instance) и надклассовых тпов, одни не полностью квалифицированные имена всегда будут "прятать" другие не полностью квалифицированные имена. Таким образом, компилятор будет требовать применение полной точечной нотации, чтобы избежать возникновение двусмысленности имен при использовании спецификатора use.

Необходимо использовать такую схему наименований, которая будет согласованной, читабельной и выразительной для представления абстракции. В идеале, схема наименований для различных способов использования тэговых типов, которые используются для построения различных абстракций, должна быть единой. Однако если используемая схема соглашений по наименованиям слишком жесткая, то использующие ее фрагменты кода будут выглядеть неестественно с точки зрения читабельности. При использовании подобных соглашений по наименованию для расширений типа, посредством наследования или подмешивания настраиваемых модулей (generic mixin), достигается читабельность описаний объектов.

6.4.6 Именование методов

Для именования методов абстракций предлагаются следующие правила:

  1. Имена методов в комбинации с их аргументами должны читаться подобно фразам английского языка.
  2. функции, возвращающие логические результаты, должны использовать предикативные предложения.
  3. Именная ассоциация должна использоваться для методов, которые имеют более одного параметра.
  4. Аргумент, который обозначает обрабатываемый методом экземпляр объекта, не должен называться также как абстракция к которой он принадлежит. Вместо этого, он должен использовать более общее имя, такое как: Affecting, Self, This ... (примечание: это правило обязательно используется только при именном ассоцировании параметров).

Следующий пример демонстрирует использование этих правил:

    
    
    Insert (The_Item     => An_Apple,
            Before_Index => 3,
            Into         => The_Fruit_List);
    
    Merge (Left  => My_Fruit_List,
           Right => The_Citrus_Fruits);
    
    Is_Empty (The_Queue); -- используется позиционная ассоциация
    

Следует учитывать, что при несоблюдении правила 4 может быть нарушена читабельность наследуемых операций. Рассмотрим следующий случай, где правило 4 нарушено:

    
    
    package List is 
        . . . 
        procedure Pop (The_Item  : out    List.Item;
                       From_List : in out List.Object); -- используется From_List
                                                        -- вместо From
    end List;
    
    package Queue is
        . . . 
        type Object is new List.Object; -- Queue.Object наследует
                                        -- операции List.Object
    end Queue;
    
    with List;
    with Queue;
    procedure Main is
    begin
        . . . 
        Queue.Pop (
          The_Item  => My_Item
          From_List => The_Queue); -- не очень "чисто":
                                   -- имя ассоциации указывает список на (List),
                                   -- а аргументом является очередь (Queue) ?!
    end Main;
    

6.4.7 Опасность наследования

Когда главный тип абстракции - производный, он наследует все примитивные методы своих предков. Иногда некоторые из унаследованных методов не будут работать или будут не правильно работать с производной абстракцией (как правило из-за того, что они не имеют представления о новых атрибутах). В подобных случаях возникает необходимость переопределения унаследованных методов.

Упущение необходимости переопределения метода является одной из широко распространенных ошибок. Такую ошибку не всегда можно обнаружить во время тестирования самостоятельного модуля, без проверки всех унаследованных методов в контексте производной абстракции.

Контролируемые объекты Ada 95 - это первые кандидаты для повышенного внимания в подобных ситуациях. Любой контролируемый атрибут, добавленный в производной абстракции, требует переопределения унаследованных подпрограмм Adjust и Finalize (как минимум). Они должны вызываться, соответственно, для подгонки и очистки новых атрибутов.

Рассмотрим следующий пример:

    
    
    package Coordinates is
    
        type Object is tagged private;
        . . .
    
        function Distance_Between
           (Left : in Coordinates.Object;
            Right: in Coordinates.Object) 
            return Distance.Object;
        . . .
    end Coordinates;
    
    
    package Position is
    
        type Object is new Coordinates.Object with private;
        . . .
    
        function Distance_Between       -- переопределяет
           (Left  : in Position.Object; -- Coordinates.Distance_Between
            Right : in Position.Object)
            return Distance.Object;
        . . .
    end Position;
    

Здесь, абстракция Position является производной от абстракции Coordinates, но метод Distance_Between должен быть переопределен для учета в векторе позиции компонента высоты.

6.5 Советы Паскаль-программистам

Ада является языком программирования, который во многих аспектах подобен языку Паскаль. Однако, Ада не является "супермножеством" средств языка Паскаль. Таким образом, синтаксис инструкций Ады несколько отличается от синтаксиса инструкций языка Паскаль и множество средств языка Паскаль реализовано в Аде несколько иначе. Здесь приводится некоторое обобщение различий языков программирования Паскаль и Ада, которое может быть полезно для программистов которые хорошо знают язык Паскаль и желают изучить язык Ада.

Самое важное отличие Ады от Паскаля заключается в том, что Ада строжайшим образом стандартизированный язык программирования, дополнительно сопряженный с процессом сертификации компилятора. Таким образом, Ада свободна от наличия различных синтактических расширений, которые можно встретить, практически, в каждой Паскаль-системе. С другой строны, стандартно описанный язык программирования Ада содержит почти все средства и особенности современных расширений языка Пасскаль.

6.5.1 Описания и их последовательность

Стандарт языка Паскаль требует использование правильного порядка следования описаний (константы, типы, переменные, подпрограммы), что ослабляется некоторыми реализациями Паскаль-систем. Ада обладает более гибкими требованиями к порядку следования описаний. Так, стандарт Ады подразумевает "базовые описания" и "поздние описания". Таким образом, к "базовым" описаниям можно отнести описания констант, типов и переменных, а к "поздним" описаниям - описания подпрограмм (процедур и функций). Следует заметить, что мы не рассматриваем остальные описания в целях упрощения. В описательной части, программы или подпрограммы, базовые описания могут быть свободно перемешаны (с естественным пониманием того, что перед тем как что-либо используется оно должно быть предварительно описано). Все базовые описания должны предшествовать всем поздним описаниям.

В Паскале, зарезервированные слова type, const и var должны появляться в описательной части только один раз. В Аде, описание каждого типа или подтипа должно соответственно начинаться с type или subtype Примером описания константы может служить следующее:

    
    
    FirstLetter: constant Character := 'A';
    

Зарезервированное слово var не используется вовсе, поэтому переменные описываются подобным образом:

    
    
    Sum : Integer;
    

Кроме того, описание типа записи, в Аде, всегда должно завершаться end record.

6.5.2 Структуры управления

Все структуры управления последовательностью выполнения (иначе, управляющие структуры) Ады имеют соответствующие закрывающие инструкции, такие как "if ... end if", "loop ... end loop", "case ... end case". Далее, в Аде символ двоеточия ';' используется для завершения инструкции, а не для разделения инструкций, как в Паскале. Это обеспечивает синтаксис, который, по сравнению с синтаксисом Паскаля, легче использовать корректно. Например, инструкция Паскаля:

    
    
    if X < Y then
        A := B;
    

будет написана в Аде следующим образом:

    
    
    if X < Y then
        A := B;
    end if;
    

А инструкция Паскаля:

    
    
    if X < Y then
        begin
            A := B;
            Z := X
        end
    else
        begin
            A := X;
            Z := B
        end;
    

будет написана в Аде следующим образом:

    
    
    if X < Y then
        A := B;
        Z := X;
    else
        A := X;
        Z := B;
    end if;
    

Использование подобного синтаксиса в Аде гарантирует остутствие "висячих" else.

Переменные управляющие циклами for всегда описываются неявно, что является единственным исключением из правил, требующих чтобы все было описано явно. Счетчик цикла for локален для тела цикла. Описание счетчика цикла for как переменной, так как это принято в Паскале, не наносит реального ущерба, но описывает самостоятельную переменную, которая спрятана за фактическим счетчиком цикла и, следовательно, не видима в теле цикла.

Диапазоны, для цикла for, часто назначаются как имена типов или подтипов, подобно следующему:

    
    
    for Count in Index'Range loop
    

Ада не имеет структуры цикла repeat. Вместо этого используется "loop ... end loop" с инструкцией выхода из цикла "exit when" в конце тела цикла.

Переменная выбора в инструкции case должна быть дискретного (целочисленного или перечислимого) типа. Различные альтернативы выбора case должны указывать все возможные значения переменной выбора в неперекрывающейся манере.

6.5.3 Типы и структуры данных

Двумерные массивы не являются массивами массивов. Следовательно, A(J)(K) не является тем же самым, что и A(J,K). Разработчики, в действительности, относят последний из двумерных массивов к массивам массивов. Смысл этого заключается в том, что существуют различные структуры Ады для которых стандарт не определяет отображение на хранение многомерного массива в памяти (ориентирование на строку или на столбец). Например, это позволяет предусмотрительным реализаторам Ада-систем использовать нелинейное отображение. На практике, большая часть существующих Ада-компиляторов использует отображение ориентирование на строку, соответствующее правилам Паскаля и Си.

В качестве типов полей записи всегда должны использоваться имена типов или подтипов, что означает, что поле записи не может иметь анонимный тип, подобно array или record. Для построения иерархических типов записей, сначала необходимо построить типы записей нижнего уровня, а затем использовать имена этих типов для описания полей записей более высого уровня.

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

Ада более жестко контролирует использование записей с вариантами чем Паскаль. Таким образом, нет возможности описать "свободное объединение", или вариантную запись без указания дискриминанта. В Паскале и Си, свободные объединения часто используются для обхода проверки типа, но такой подход не может быть использован в Аде (Ада имеет настраиваемую функцию Unchecked_Conversion, которая может быть использована для действительного обхода проверки типа).

В отличие от Паскаля, Ада не имеет предопределенного средства построения типа множества set. Однако, средства Ады обеспечивают программисту возможность построить подобный тип самостоятельно.

6.5.4 Совместимость типов и подтипов

Важно помнить и понимать, что Ада использует именную, а не структурную эквивалентность типов. Например, пусть даны следующие описания:

    
    
    A, B: array(1..10) of Float;
    C   : array(1..10) of Float;
    

Тогда, следующие инструкции присваивания:

    
    
    A := B;
    C := B;
    

будут недопустимыми, поскольку все три массива имеют различные анонимные типы, назначенные компилятором (некоторые компиляторы Паскаля будут позволять осушествление первого присваивания). Для обеспечения возможности присваивания массивов, необходимо явное указание имени для типа массива:

    
    
    type List is array(1..10) of Float;
    
    A, B: List;
    C: List;
    

Теперь оба показанных выше присваивания будут допустимы. При программировании на Аде, настоятельно рекомендуется избегать использования анонимных типов, подобно тому как это делается в Паскале.

6.5.5 Параметры подпрограмм

Режимы передачи параметров "in", "out" и "in out". для подпрограмм Ады, являются грубыми эквивалентами режимов передачи параметров по значению и по ссылке (var) для подпрограмм Паскаля.

В теле подпрограммы, "in"-параметры могут быть только прочитаны. Основное отличие между "out" и "in out" параметрами заключается в том, что текущее значение фактического "in out"-параметра передается в процедуру, а текущее значение фактического "out"-параметра в процедуру не передается и, следовательно, может считаться неопределенным до указания значения внутри подпрограммы. Функции Ады не могут иметь "out" и "in out" параметры.

В случае передачи в подпрограмму массива, использование режима передачи параметров "in out" не обеспечивает никакого выигрыша в эффективности по сравнению с режимом передачи параметров "in". Подобный прием является широко распространенным в языке Паскаль, когда большие массивы передаются как var-параметры. Правила языка Паскаль требуют, чтобы var-параметры передавались по ссылке, а параметры передаваемые по значению - копировались. Ада использует другие правила: параметры скалярных типов всегда передаются по значению/результату не зависимо от указанного режима передачи параметров. Ада допускает, чтобы параметры составных типов (массивы и/или записи) также передавались по значению/результату, но реальные Ада-компиляторы никогда не так не делают, в особенности, когда составной тип имеет большие размеры. Таким образом, реальные Ада-компиляторы передают в подпрограммы массивы и записи больших размеров по ссылке, даже если для них указан режим передачи параметров "in". Поскольку "in"-параметры гарантировано не могут быть записаны, то не существует опасности непреднамеренного изменения содержимого параметра внутри вызванной подпрограммы.

В случае использования режимов "in" и "in out" для передачи параметров скалярных типов, значения копируются обратно в вызвавшую подпрограмму после нормального завершения вызвынной процедуры. Таким образом, если вызов процедуры заканчивается распространением исключения в вызвавшую подпрограмму, то значения параметров не копируются обратно, в вызвавшую подпрограмму, и, следовательно, вызвавшая подпрограмма сохраняет оригинальные значения (те которые были до вызова процедуры).

Инструкции ввода/вывода Ады, являются обычными вызовами процедур, которые подразумевают, что только одиночные целочисленные, вещественные, символьные, строковые или перечислимые значения могут быть прочитаны или отображены с помощью каждого вызова Get или Put. При написании инструкций ввода/вывода, для этих процедур нельзя предоставлять произвольное число параметров, как это допускается в Паскале. Попытка выполнения этого, приведет к естественной ошибке компиляции, говорящей о не совпадении числа параметров ("unmatched procedure call"), когда компилятор пытается найти версию процедуры Get или Put, с соответствующим числом параметров.

6.5.6 Пакеты Ады и их соответствие модулям Паскаля

Реально, модули Паскаля (unit) не являются частью стандарта ISO Pascal. Однако, их использование предусматривается многими современными расширениями языка Паскаль, например, версиями Паскаль-систем от Borland, Symantec или Free Pascal. Модули Паскаля являются грубым эквивалентом пакетов Ады с двумя важными отличиями:

6.5.7 Использование "is"
        и символа точки с запятой ';'

Бесконечное горе ждет тех пользователей Ады, которые путают назначение символа точки с запятой ';' с назначением "is". При использовании некоторых компиляторов, это приводит к длинной последовательности сообщений об ошибках. Наиболее вероятен случай использования точки с запятой вместо is - при описании подпрограмм, также как это делается в языке Паскаль:

    
    
    procedure Do_Something(X : Integer); --   <---- это подразумевает проблему!!!
      
      -- описания
    
    begin
      
      -- инструкции
    
    end Do_Something;
    

Проблема заключается в том, что такое использование символа точки с запятой допустимо, но смысл его отличается от того, что вы ожидаете. Строка:

    
    
    procedure Do_Something(X : Integer);
    

является, в реальности, описанием спецификации процедуры, что в языке Паскаль больше похоже на опережающее описание процедуры с помощью директивы forward. Следовательно, перепутывание символа точки с запятой ';' с is почти гарантировано приводит к порождению большого количества сообщений об ошибках компилятора, поскольку синтаксический анализатор компилятора Ады будет трактовать такую инструкцию как спецификацию подпрограммы и будет сбит с толку появлением последующего блока begin-end, который ошибочно располагается вне контекста. Появление is точно указывает Ада-компилятору на то, что далее следует тело подпрограммы.

Спецификации подпрограмм, являющиеся частью спецификации пакета, могут рассматриваться в контексте опережающих описаний, для чего в Паскале обычно используется директива forward. В этом случае, первая строка тела подпрограммы (в теле пакета) должна быть идентична спецификации, за исключением того, что символ точки с запятой ';' заменяется на is. Это отличается от Паскаля, где список параметров может не повторяться.


Copyright (C) А.Гавва V-0.4w май 2004