Ada:如何迭代私有地图?

Ada: How to Iterate over a private map?

考虑以下几点:

with Ada.Containers.Hashed_Maps;
with Ada.Containers; use Ada.Containers;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
   
   package Tiles is
      -- Implementation is completely hidden
      type Tile_Type is private;
      type Tile_Set is tagged private;
      type Tile_Key is private;
      
      procedure Add (Collection : in out Tile_Set; Tile : Tile_Type);
      function Get (Collection : in Tile_Set; Key : Natural) return Tile_Type;
      function Make (Key : Natural; Data : Integer) return Tile_Type;
      function Image (Tile : Tile_Type) return String;

   private
      
      type Tile_Key is record
         X : Natural;
      end record;
      
      function Tile_Hash (K : Tile_Key) return Hash_Type is
        (Hash_Type (K.X));
      
      type Tile_Type is record
         Key    : Tile_Key;
         Data   : Integer;
      end record;
    
      package Tile_Matrix is new Ada.Containers.Hashed_Maps
        (Element_Type        => Tile_Type,
         Key_Type            => Tile_Key,
         Hash                => Tile_Hash,
         Equivalent_Keys     => "=");

      use Tile_Matrix;

      type Tile_Set is new Tile_Matrix.Map with null record;

   end Tiles;
   
   package body Tiles is
      
      procedure Add (Collection : in out Tile_Set; Tile : Tile_Type) is
      begin
         Collection.Include (Key => Tile.Key, New_Item => Tile);
      end Add;
      
      function Get (Collection : in Tile_Set; Key : Natural) return Tile_Type is
         K       : Tile_Key := (X => Key);
         C       : Cursor := Collection.Find (Key => K);
      begin -- For illustration, would need to handle missing keys
         return Result : Tile_Type do
            Result := Collection (C);
         end return;
      end Get;
      
      function Image (Tile : Tile_Type) return String is
        (Tile.Key.X'Image & '=' & Tile.Data'Image);
      
      function Make (Key : Natural; Data : Integer) return Tile_Type is
         New_Key : Tile_Key := (X => Key);
      begin
         return Result : Tile_Type do
            Result.Key := New_Key;
            Result.Data := Data;
         end return;
      end Make;
   end Tiles;
   
   use Tiles;

   S  : Tile_Set;
   T  : Tile_Type;
   
begin
   S.Add (Make (Key => 1, Data => 10));
   T := S.Get (1);
   Put_Line (Image (T)); -- 1, 10
   
   S.Add (Make (Key => 2, Data => 20));
   T := S.Get (2);
   Put_Line (Image (T)); -- 1, 20
   
   for X in S loop -- Fails: cannot iterate over "Tile_Set"
                   --     +: to iterate directly over the elements of a container, write "of S"
                   -- but "for X of S" doesn't work either.
      T := S (X);  -- Fails: array type required in indexed component
                   -- presumably because X isn't a cursor?
      Put_Line (Image (T));
   end loop;
end;

在我看来,编译器有足够的知识来迭代 Tile_Set,我想它不会,因为我没有公开迭代器。

我该如何修改才能使 'for X is S loop' 有效?

更一般地说,隐藏底层容器的实现,同时公开索引、迭代等的习惯用法是什么?

It seems to me that the compiler has enough knowledge to iterate over a Tile_Set and I'm supposing it won't because I haven't exposed an iterator.

该评估是正确的。为了能够循环一个类型,该类型需要定义 Default_IteratorIterator_Element 方面,如 LRM 5.5.1, and the aspect Constant_Indexing as described in LRM 4.1.6 中所述。两节阅读

These aspects are inherited by descendants of type T (including T'Class).

这意味着由于 Tile_Set 继承自 Tile_Matrix.Map,它确实继承了该地图上定义的这些方面。但是,由于继承关系是私有的,所以方面在该包之外是不可见的。

您也不能将它们显式设置为私有类型,因为 4.1.6 说

The aspects shall not be overridden, but the functions they denote may be.

将它们设置为私有类型会覆盖私有部分中继承的方面。

你有两个选择:

  • 创建继承关系public以便您可以立即访问所有方面。
  • 使Tile_Set封装Hashed_Map值,这样你就可以在类型上实现自己的迭代。

第二个选项如下所示:

   type Cursor is private;

   type Tile_Set is private
     with Default_Iterator => Iterate,
          Iterator_Element => Tile_Type,
          Constant_Indexing => Constant_Reference;

   function Has_Element (Position: Cursor) return Boolean;

   package Tile_Set_Iterator_Interfaces is new
     Ada.Iterator_Interfaces (Cursor, Has_Element);

   type Constant_Reference_Type
     (Element : not null access constant Tile_Type) is private
      with Implicit_Dereference => Element;

   function Iterate (Container: in Tile_Set) return 
     Tile_Set_Iterator_Interfaces.Forward_Iterator'Class;

   function Constant_Reference (Container : aliased in Tile_Set;
                                Position  : Cursor)
     return Constant_Reference_Type;
private
   
   -- ..
   
   type Cursor is record
      Data : Tile_Matrix.Cursor;
   end record;

   type Tile_Set is record
      Data : Tile_Matrix.Map;
   end record; 

在这些子程序的实现中,您可以简单地委托给Tile_Matrix个子程序。

教训是当你的实际意图是组合时你不应该继承。