Ada: создать тип универсального экземпляра (например, векторы)

#types #rename #ada

#типы #переименовать #ada

Вопрос:

Я пытаюсь создать тип «Путь», который представляет собой вектор точек:

 with Ada.Containers.Vectors;
use Ada.Containers;
package Geometry is

    type Point is record
        North : Integer := 0; -- millimetres
        East  : Integer := 0;
    end record;
    function Same_Point (A, B : Point) return Boolean is
      (A.North = B.North and then A.East = B.East);

    package Path_Points is new Vectors
      (Element_Type => Point,
       Index_Type   => Positive,
       "="          => Same_Point);
    use Path_Points;
    -- works, but poor readability. 'result' is a 'path', not a vector
    Result : Vector; 
    
    -- What I'd like to do, a type called 'Path':
    type Path is new Path_Points.Vector; -- WRONG

end Geometry;
 

Чтобы, когда я использую геометрию amp;, я мог писать

     Result : Path; -- Easy to understand!
 

Как я могу создать тип ‘Path’, который является просто псевдонимом для Ada.Containers.Vectors.Vector?

Я понимаю, что я мог бы создать в стиле ООП «Путь частного типа» и реализовать все возможности для репликации функций Vectors, но это кажется ужасно неуклюжим.

Комментарии:

1. «»Путь частного типа» и реализовать все возможности для репликации функций Vectors». Нет, вы бы реализовали полный тип и его операции в терминах скрытого экземпляра векторов. Это правильный маршрут, если вы хотите, чтобы геометрия пакета реализовывала абстракцию пути.

2. Почему вы определяете Same_Point ? "=" определяется для Point и делает то же самое.

Ответ №1:

Vector тип — это tagged тип, что означает, что вам нужно расширение типа, а не просто производное:

 type Path is new Path_Points.Vector with null record;
 

Я предполагаю, что компилятор намекнул на это в сообщении об ошибке?

РЕДАКТИРОВАТЬ: прежде чем комментарии ниже затянутся на более длительное обсуждение, позвольте мне прояснить некоторую путаницу:

У типов нет имен в Ada, есть только подтипы, поэтому объявление типа (включая производные типов, которые создают новые типы) на самом деле является объявлением анонимного типа наряду с its first subtype .

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

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

таким образом

 type Foo is null record;
procedure Process(Item : in out Foo);
 

является концептуально:

 type @anonymous_type is null record;
procedure Process(Item : in out @anonymous_type);

subtype Foo is @anonymous_type; 
 

и

 type Bar is new Foo;
 

является концептуально:

 type @derived_type is new @anonymous_type;
procedure Process(Item : in out @derived_type); -- implicit declaration of inherited subprogram

subtype Bar is @derived_type;
 

в то время как

 subtype Baz is Foo; 
 

это просто. Новое имя.

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

 with Ada.Strings.Unbounded;
package Foo is
   subtype Bar is Ada.Strings.Unbounded.Unbounded_String;
   -- Bar is a subtype; no inheritance, must call operations on the base type
   Foobar : constant Bar := Ada.Strings.Unbounded.To_Unbounded_String("Foobar");
  
   type Baz is new Ada.Strings.Unbounded.Unbounded_String;
   -- Baz is a derived type, inherited operations are directly visible in its scope
   Foobaz : constant Baz := To_Unbounded_String("Foobaz");


   -- However:
  
   -- This is not legal for a subtype (without a `use` clause):
   -- Foobar : constant Bar := To_Unbounded_String("Foobar");


   -- This is not legal for a derived type (without an explicit type conversion):
   --  Foobaz : constant Bar := Ada.Strings.Unbounded.To_Unbounded_String("Foobaz");
end Foo;
 

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

Аналогично, производные типы могут уменьшить требуемую типизацию и количество зависимостей (и, возможно use , предложений), необходимых для любых пользователей вашего API:

Обратите внимание, как подтип принуждает пользователя к with области базового типа

 with Foo;
with Ada.Strings.Unbounded;
procedure Bar_Test is
   Bar : Foo.Bar := Ada.Strings.Unbounded.To_Unbounded_String("Foobar");

   -- illegal for a subtype
   -- Bar : Foo.Bar := Foo.To_Unbounded_String("Foobar");
begin
   null;
end Bar_Test;
 

Где производный тип не

 with Foo; -- Baz is a derived type, inherited operations are visible in Foo
procedure Baz_Test is
   Baz : Foo.Baz := Foo.To_Unbounded_String("Foobaz");

   -- illegal for a derived type, type conversion needed
   -- Baz : Foo.Baz := Ada.Strings.Unbounded.To_Unbounded_String("Foobaz");
begin
   null;
end Baz_Test;
 

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

 type Foo is tagged
record
   M1 : Natural;
end record;

procedure Process(Item : in out Foo);


type Bar is new Foo with
record
   M2 : Natural; -- Bar has both members M1 and M2
end record;

-- implicit declaration of Process for Bar, unless Process is overridden (as for Baz below)


type Baz is new Foo with null record; -- no new members, Baz has only M1

overriding
procedure Process(Item : in out Baz); -- Process is overridden 
 

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

Помеченные типы и расширения типов также позволяют осуществлять динамическую диспетчеризацию для класса типов

 Dispatch : Foo'Class := Baz'(Foo with M1 => 1337); 
 

Foo'Class называется общеклассовым типом и может содержать a Foo или любой другой тип, укорененный в Foo (производный / расширенный от Foo )

Они также позволяют использовать более продвинутые функции, такие как множественное наследование в виде interface представлений подпрограмм с префиксами (более известных как Объект.Точечная нотация), Управляемые типы и т.д.

Комментарии:

1. Возможно, лучше subtype Path is Path_Points.Vector ?

2. Да, я понял, что это намек, но я не мог найти ответа («вам нужно расширение типа, а не просто производное» — это совсем не очевидно). Спасибо за быстрые ответы, джентльмены, оба очень поучительные, я очень благодарен. Ада сложна для новичков, да? >;-)

3. Можно использовать объявление подтипа как средство для «переименования» другого подтипа.

4. @JimRogers, @SimonWrighht Да, но есть тонкие различия. Производный тип создает новый отдельный тип, поэтому нет путаницы между яблоками и апельсинами, а подтип — нет. И производный тип наследует примитивные операции родительского элемента, тогда как подтип — нет. Это будет иметь влияние при отображении такого типа в API пакета, в большинстве случаев я бы хотел сделать Foo := Geometry.To_Vector(...); , а не Foo := Geometry.Path_Points.To_Vector(...);

5. @smirkingman, я пытался намекнуть, что вы должны были указать фактическое сообщение об ошибке в своем вопросе, а не просто -- WRONG 😉