Матрёшка: контейнер для объектов различных типов

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

Для передачи значения произвольного типа необходимо использовать тип Holder, объявленный в пакете League.Holders (одно небольшое замечание: в Матрёшке 0.1.x тип называется Value, а пакет — League.Values).

Типовой способ использования объектов типа Holder можно рассмотреть на примере передачи значения строкового типа:


          

          
with Ada.Wide_Wide_Text_IO;
with League.Holders;
with League.Strings;

procedure Example_1 is
   procedure Put (Item : League.Holders.Holder) is
   begin
      if Item.Is_Empty then
         Ada.Wide_Wide_Text_IO.Put_Line ("Holder is empty");

      else
         declare
            Str : constant League.Strings.Universal_String
              := League.Holders.Element (Item);
         begin
            Ada.Wide_Wide_Text_IO.Put_Line (Str.To_Wide_Wide_String);
         end;
      end if;
   end Put;

   S : constant League.Strings.Universal_String
     := League.Strings.To_Universal_String ("Hello, world!");
   H : League.Holders.Holder;

begin
   Put (H);
   H := League.Holders.To_Holder (S);
   Put (H);
end Example_1;

Созданный по умолчанию объект Holder не содержит никакого значения, а функция Is_Empty возвратит True для такого объекта. Поэтому первой строкой вывода примера будет

Holder is empty

А вот созданный с помощью функции To_Holder объект уже содержит значение, Is_Empty возвратит для него False, а с помощью функции Element будет получено собственно хранящееся значение. Функция Element возбудит исключение Constraint_Error в случае отсутствия значения или несовместимости типа значения. Функции To_Holder и Element перегружены для обеспечения возможности обработки значений различных типов данных.

Обработка пользовательских типов данных

Для обеспечения возможности хранения пользовательских типов данных в объектах Holder необходимо настроить один из настраиваемых пакетов:

  • League.Holders.Generic_Enumerations — для перечислимых типов;
  • League.Holders.Generic_Floats — для вещественных типов;
  • League.Holders.Generic_Integers — для целочисленных типов;
  • League.Holders.Generic_Holders — для произвольных типов данных.

В настроенном пакете содержатся реализации подпрограмм для установки и получения значения указанного пользовательского типа данных.

Небольшой пример:


          

          
with League.Holders.Generic_Integers;

procedure Example_2 is
   type My_Integer is range 0 .. 10;

   package My_Integer_Holders is
     new League.Holders.Generic_Integers (My_Integer);

   H : League.Holders.Holder;
   V : My_Integer;

begin
   H := My_Integer_Holders.To_Holder (10);
   V := My_Integer_Holders.Element (H);
end Example_2;

Для стандартных типов данных перечисленные пакеты уже настроены, это:

  • League.Holders.Booleans для типа Boolean;
  • League.Holders.Floats для типа Float;
  • League.Holders.Integers для типа Integer;
  • League.Holders.Long_Floats для типа Long_Float;
  • League.Holders.Long_Integers для типа Long_Integer;
  • League.Holders.Short_Integers для типа Short_Integer.

Типизация

Объекты типа Holder имеют ещё одно важное свойство — ассоциированный тип данных. Он представлется в виде объектов типа Tag объявленного в том же пакете.

Созданный по умолчанию объект Holder не имеет ассоциированного типа равно как и значения. Тип может быть ассоциирован явно с помощью подпрограммы Set_Tag, при этом объект станет типизированным, но всё так же не будет иметь значения. Значение для объекта Holder с ассоциированным типом может быть установлено с помощью подпрограммы Replace_Element, а процедура Clear позволяет удалить значение. Подпрограмма Replace_Element возбудит исключение Constraint_Error при вызове над объектом ассоциированным с несовместимым типом данных. Подпрограмма Clear не меняет ассоциированного типа.

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

В общем и целом ассоциированные типы не совместимы друг с другом. Это означает, что процедура Replace_Element и функция Element возбудят исключение Constraint_Error при вызове с использованием типа отличного от ассоциированного с объектом Holder. Однако, имеет место быть два послабления — для целочисленных и вещественных типов. В пакете League.Holders объявлены два специализированных типа данных Universal_Integer и Universal_Float, которые могут быть использованы для установки/получения значения любого целочисленного или вещественного типа. Тем не менее это не отменяет того факта, что будет возбуждено исключение Constraint_Error при попытке установить значение за пределами допустимого конкретным типом диапазона.

Например, следующий код будет выполнен без возбуждения исключений:


          

          
with League.Holders.Integers;

procedure Example_3 is
   H : League.Holders.Holder;
   V : League.Holders.Universal_Integer;

begin
   H := League.Holders.Integers.To_Holder (10);
   --  Объект Holder имеет тип Standard.Integer.
   V := League.Holders.Element (H);
   --  Значение извлекается как Universal_Integer.
end Example_3;

Заключение

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


Автор: Вадим Годунко
Дата: 22.07.2012