Ada 202x (6日目) - container aggregate

では大物いきますか。 ユーザー定義コンテナのaggregate式です。

経緯

これまで、ユーザー定義コンテナを式ひとつで初期化できるケースは限られていました。 主に空の Empty_* と中身を全部同じ値にする To_* だけでした。

ですので、配列であればaggregate式で書ける定数でもコンテナに入れようと思えば後から AppendInsert 等を呼んで要素を足していくしかありませんでした。

declare
   package Float_Vectors is new Ada.Containers.Vectors (Positive, Float);
   L : Float_Vectors.Vector := Float_Vectors.Empty_Vector (Capacity => 3);
begin
   Float_Vectors.Append (L, 1.0);
   Float_Vectors.Append (L, 2.0);
   Float_Vectors.Append (L, 2.0);
declare
   package Float_Maps is new Ada.Containers.Ordered_Maps (Positive, Float);
   M : Float_Maps.Map := Float_Maps.Empty_Map;
begin
   Float_Maps.Insert (M, 1, 1.0);
   Float_Maps.Insert (M, 2, 2.0);
   Float_Maps.Insert (M, 3, 2.0);
declare
   package Float_Sets is new Ada.Containers.Ordered_Sets (Float);
   S : Float_Sets.Set := Float_Sets.Empty_Set;
begin
   Float_Sets.Insert (S, 1.0);
   Float_Sets.Insert (S, 2.0);
   Float_Sets.Insert (S, 3.0);

Ada 202xでの改善

コンテナがaggregate式に対応したことで次のように書けます。

Vector の例。

declare
   L : Float_Vectors.Vector := [1 => 1.0, 2 .. 3 => 2.0];

pygments以下略。

Map の例。

declare
   M : Float_Maps.Map := Float_Maps.Empty_Map :=
     [for I in 1 .. 2 => Float (I), 3 => 2.0];

Map の例をもうひとつ。 キーが文字列の場合。

declare
   package String_Maps is
     new Ada.Containers.Indefinite_Ordered_Maps (String, String);
   M : String_Maps.Map :=
     ["カタナ" => "火迅風魔刀",
      "剛剣マンジカブラ" => "秘剣カブラステギ",
      "風磨の盾" => "螺旋風魔の盾"];

もしキーがループできない型であってもparameterized array component associationを使うことができます。 配列ではインデックスが常にループできますので説明しそびれました。 use を用いてキーを指定します。 また予約語の再利用ですね……。

declare
   Key : constant array (1 .. 3) of access constant String :=
     [new String'("カタナ"),
      new String'("剛剣マンジカブラ"),
      new String'("風磨の盾")];
   Value : constant array (1 .. 3) of access constant String :=
     [new String'("火迅風魔刀"),
      new String'("秘剣カブラステギ"),
      new String'("螺旋風魔の盾")];
   package String_Maps is
     new Ada.Containers.Indefinite_Ordered_Maps (String, String);
   M : String_Maps.Map :=
     [for I in 1 .. 3 use Key (I).all => Value (I).all];

Set の例。

declare
   S : Float_Sets.Set := Float_Sets.Empty_Set := [1.0, 2.0, 3.0];

Aggregateアスペクト

この機能は標準ライブラリに限らずユーザー定義コンテナ全般で使うことができます。 自前のコンテナで対応したい場合は、当然ながら記述を書き足す必要があります。

Ada 202xでは複数のサブプログラムを組み合わせて実現するようになっています。 型宣言に Aggregate アスペクトを用いて使用するサブプログラムを指定します。

Vector の宣言はこうなっています。

type Vector is tagged private
  with
    -- 他の多数のアスペクトは省略
    Aggregate =>
      (Empty          => Empty,
       Add_Unnamed    => Append_One,
       New_Indexed    => New_Vector,
       Assign_Indexed => Replace_Element);
type Vector is tagged private
  with
    -- 他の多数のアスペクトは省略
    Aggregate =>
      (Empty          => Empty,
       Add_Unnamed    => Append,
       New_Indexed    => New_Vector,
       Assign_Indexed => Replace_Element);

先程の例は

declare
   L : Float_Vectors.Vector := [1 => 1.0, 2 .. 3 => 2.0];

このように展開されます。

declare
   L : Float_Vectors.Vector := Float_Vectors.New_Vector (1, 3);
begin
   Replace_Element (L, 1, 1.0);
   for I in 2 .. 3 loop
      Replace_Element (L, I, 2.0);
   end loop;

配列同様にインデックスを用いる場合のnamed associationは New_Indexed で確保した範囲に Assign_Indexed を使って代入していく形に展開されます。

Map の宣言はこうなっています。

type Map is tagged private
  with
    -- 他の多数のアスペクトは省略
    Aggregate =>
      (Empty     => Empty,
       Add_Named => Insert);

配列同様ではない場合のnamed associationは EmptyAdd_Named で要素をひとつずつ足していく形に展開されます。 Vector と異なり *_Indexed ではありませんが、parameterized array component associationは使用できます。

Set の宣言はこうなっています。

type Set is tagged private
  with
    -- 他の多数のアスペクトは省略
    Aggregate =>
      (Empty       => Empty,
       Add_Unnamed => Include);

positional associationは EmptyAdd_Unnamed で要素をひとつずつ足していく形に展開されます。

この3種類が想定されているパターンとなります。

要素の追加に使われている Replace_ElementInsertInclude は既存のサブプログラムがそのまま使われています。 Empty はAda 202xで追加されるものですが、これも既存の Empty_* で代用可能です。 Empty_* は定数として宣言されていますので、Capacityを指定できる関数として Empty が追加されたというだけです。 Vector の Append_One も Length パラメータを取らない版というだけで既存の Append と同じですので、実質 New_Vector のみがaggregate式のために追加されたものとなります。

勿論 New_Vector もそれ自体の用途で使用可能です。 (Vector の場合は用途が思いつかないですが、範囲を与えて空のコンテナを作る関数が有用なケースがあるかもしれません、たぶん。)

このように、aggregate式に対応するために専用のサブプログラムを必要としないようになっています。 似たような機能があるC++ではinitializer_listを受け取るコンストラクタによって実現されています。

Ada.Containers.Vectors.Empty

Ada.Containers.Vectors.Append_One

Vectors.Append は要素を追加する版とコンテナの全要素を追加する版でオーバーロードされていて曖昧さの解決のため以前 Append_One が提案されていました。 現在の提案では Append をそのまま使用して、代わりにコンテナを追加する版が Append_Vector として分離されるようです。

関連AI

  • AI12-0212-1 Container aggregates; generalized array aggregates

  • AI12-0339-1 Empty function for Container aggregates

所感

これだけですとまあ構文糖というだけなのですが、Ada 202xではaggregate式に大幅な手が入っておりこれと2日目のparameterized array component associationを元に更に色々できます。 配列にはインデックスが連続でなければならない等の制限がありますので、色々やるならそうした制限を突破できるユーザー定義コンテナでやる必要があります。 ですのでユーザー定義コンテナがaggregate式に対応するのは必須なのかもしれません。 個人的にはAda 2012以降のアスペクトを使ったいかにも後付けっぽい構文糖には、あまり増えてほしくないのですけれどね。