如何编写通用实例化的规范?

How do I write the specification for a generic instantiation?

我将从 Ada 中通用过程的经典示例开始:

-------------------------
--  swaps.ads
-------------------------
package Swaps is
  generic
    type E is private;
  procedure Generic_Swap (Left, Right : in out E);
end Swaps;
-------------------------
--  swaps.adb
-------------------------
package body Swaps is
  procedure Generic_Swap (Left, Right : in out E) is
    Temporary : E;
  begin
    Temporary := Left;
    Left := Right;
    Right := Temporary;
  end Generic_Swap;
end Swaps;

现在假设我想实现一个专门的 String_Swap 程序来交换字符串,并将它提供给我的包的所有用户。我可以将以下内容添加到 swaps.adb 中的正文声明中:

procedure String_Swap is new Generic_Swap (String);

但是,如果我在 swaps.ads 中的规范中不添加任何内容,那么没有包可以使用此过程。例如:

-------------------------
--  main.adb
-------------------------
with Swaps; use Swaps;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
   First  : String := "world!";
   Second : String := "Hello, ";
begin
   String_Swap (First, Second); -- #Error: String_Swap is undefined#
   Put_Line (First);
   Put_Line (Second);
end Main;

我已经尝试将过程类型添加到规范中:

procedure String_Swap (Left, Right : in out String);

但随后 Ada 抱怨该规范缺少正文并且 swaps.adb 中的定义与之冲突。

Swaps 的用户唯一能看到的就是规范。由于规范中没有关于 String_Swap 的任何内容,因此包 body 中的任何摆弄都不会产生任何影响。

如果您想 "implement a specialized String_Swap procedure for swapping strings",您必须将其包含在规范中:

package Swaps is
   generic
      type E is private;
   procedure Generic_Swap(Left, Right : in out E);
   procedure String_Swap is new Generic_Swap(String);
end Swaps;

事实证明这是一个不好的例子:当用 -gnatl 编译时,我们得到

 1. package Swaps is
 2.    generic
 3.       type E is private;
 4.    procedure Generic_Swap(Left, Right : in out E);
 5.    procedure String_Swap is new Generic_Swap(String);
                                                 |
    >>> actual for "E" must be a definite subtype

 6. end Swaps;

这是因为String类型是不确定的,也就是说,一个特定的String有一个特定的长度,只能分配给另一个String(或一个片段的一部分) String) 长度相同;所以即使你的过程 Main 是在没有使用泛型的情况下写出来的,它也会在运行时因约束错误而失败。在 ARM A.4.5 查看 Ada.Strings.Unbounded

所以,尝试确定类型:

package Swaps is
   generic
      type E is private;
   procedure Generic_Swap(Left, Right : in out E);
   procedure Character_Swap is new Generic_Swap(Character);
end Swaps;

不幸的是,

 1. package Swaps is
 2.    generic
 3.       type E is private;
 4.    procedure Generic_Swap(Left, Right : in out E);
 5.    procedure Character_Swap is new Generic_Swap(Character);
       |
    >>> warning: cannot instantiate "Generic_Swap" before body seen
    >>> warning: Program_Error will be raised at run time

 6. end Swaps;

解决方案必须单独实例化:也许在库级别,

with Swaps;
procedure Character_Swap is new Swaps.Generic_Swap(Character);

让您的用户按照他们的意愿实例化泛型会容易得多。

您不能将泛型用于 String 类型,因为它是一个不受约束的类型。但是让我们改用 Ada.Strings.Unbounded.Unbounded_String

您需要做的是:

  1. 在包规范中发布合适程序的规范。
  2. 通过调用内部实例化的通用过程来实现 public 过程。
with Ada.Strings.Unbounded;

package Swaps is
   generic
      type Element_Type is private;
   procedure Generic_Swap (Left, Right : in out Element_Type);

   procedure Swap (Left, Right : in out Ada.Strings.Unbounded.Unbounded_String);
end Swaps;
package body Swaps is

   procedure Generic_Swap (Left, Right : in out Element_Type) is
      Temporary : Element_Type;
   begin
      Temporary := Left;
      Left      := Right;
      Right     := Temporary;
   end Generic_Swap;

   procedure Swap_Unbounded_Strings is
     new Generic_Swap (Element_Type => Ada.Strings.Unbounded.Unbounded_String);

   procedure Swap (Left, Right : in out Ada.Strings.Unbounded.Unbounded_String) is
   begin
      Swap_Unbounded_Strings (Left  => Left,
                              Right => Right);
   end Swap;
end Swaps;

但总的来说,我更喜欢将泛型的实例化与这些泛型的规范和实现完全分开。

我要解决这个问题的方法是使用子包:

with Ada.Strings.Unbounded;

package Swaps.Instances is
   procedure Swap is new Generic_Swap (Element => Character);
   procedure Swap is new Generic_Swap (Element => Ada.Strings.Unbounded.Unbounded_String;
   ...
end Swaps.Instances;

请注意,可以编写处理不确定类型的泛型:

generic
   type Element (<>) is private;

并将 Generic_Swap 的正文更改为

procedure Generic_Swap (Left, Right : in out Element) is
   Temp : constant Element := Left;
begin -- Generic_Swap
   Left := Right;
   Right := Temp;
end Generic_Swap;

但要使用它,实际对象必须不受约束或具有相同的子类型。

I've tried to add the type of the procedure in to the specification:

procedure String_Swap (Left, Right : in out String);

but then Ada complains that this specification has a missing body and that the definition in swaps.adb conflicts with it.

这是个好主意,但实例化无法完成过程声明(编译器这么说)。您需要的是使用重命名过程:

   procedure String_Swap_Inst is new Generic_Swap (String);

   procedure String_Swap (Left, Right : in out String)
     renames String_Swap_Inst;

此外,为了能够将 String 与您的泛型一起使用,您需要稍微更改它以允许不受约束的类型:

package Swaps is
   generic
      type E (<>) is private;
   procedure Generic_Swap (Left, Right : in out E);

   procedure String_Swap (Left, Right : in out String);
end Swaps;

package body Swaps is
  procedure Generic_Swap (Left, Right : in out E) is
    Temporary : E := Left;
  begin
    Left := Right;
    Right := Temporary;
   end Generic_Swap;

   procedure String_Swap_Inst is new Generic_Swap (String);

   procedure String_Swap (Left, Right : in out String)
     renames String_Swap_Inst;
end Swaps;

当然你只能用这种方式交换等长的字符串,否则你会得到一个 Constraint_Error 就像在赋值中一样:

X : String (1 .. 2) := "123";  --  Constraint_Error!!!