Ada 2020 (3日目) - generalized array aggregate

Ada 2020ではaggregate式に大幅な手が入ってます。 その中でも、昨日少し触れましたように今回紹介するこれが一番影響が大きいのでさっさと済ませます。

経緯

従来Adaではcomposite typeに対するaggregate式はこのように書いていました。

declare
   type Point is record
      X, Y : Float;
   end record;
   type Vector is array (Positive range <>) of Float;
   P1 : Point := (X1, Y1); -- positional association
   P2 : Point := (X => X2, Y => Y2); -- named association
   A3 : Vector (1 .. 3) := (1.0, 2.0, 2.0); -- positional association
   A4 : Vector (1 .. 3) := (1 => 1.0, 2 .. 3 => 2.0); -- named association

要素の名前を書かずにソースコードに書かれた順番で埋めていくのをpositional association、要素の名前を明示するのをnamed associationといいます。 配列の場合に単一要素ではなく範囲を使うのもnamed associationです。

さて、ここで使われる括弧は他と同じ丸括弧 ( ) です。 要素数が2個以上の時は中に , がありますのでaggregate式とわかります。 しかし要素数が1個の場合は優先順位の括弧と区別が付きません。 そうです、要素数が1個のaggregate式をpositional associationで書くことができませんでした。 => があればaggregate式とわかりますのでnamed associationで書くことはできます。

declare
   type Wrapper is record
      Element : Integer;
   end record;
-- W5 : Wrapper := (0); -- error
   W6 : Wrapper := (Element => 0);
-- A7 : Vector (1 .. 1) := (1.0); -- error
   A8 : Vector (1 .. 1) := (1 => 1.0);

要素数が0の場合は、record型の場合は null record を使い、配列の場合は 'First > 'Last になるような範囲を指定する必要があります。

declare
   type Unit is null record;
-- U9 : Wrapper := (); -- error
   UA : Wrapper := (null record);
   AB : Vector (1 .. 0) := (); -- error
   AC : Vector (1 .. 0) := (1 .. 0 => <>);

長さ0の配列でも、記法上ダミーの値を書く必要があります。 といっても具体的な値を用意する必要はなくて、デフォルト初期化を意味する <> が使えます。 また、この例の様に代入先の型によりインデックスの範囲が確定している場合は、範囲を繰り返す必要はなくて others が使えます。

declare
   AD : Vector (1 .. 0) := (others => <>);

空の括弧 () が禁止されているのは理由がありまして、Adaではなるべく値を書くべきところに何も書いていなければエラーになるように文法を設計する、という方針によるものです。 記述が冗長な反面、ミスを含むコードが意図に沿わない形でコンパイルを通ってしまうようなことが少なくなっていて型システムとは違う形で堅牢性を支えています。 (このサイトに訪れてくださる方は難解コードコンテスト等が好きな方が多いと思いますので、つまらないかもしれませんね。)

なお要素数0のrecordが null record なら配列も null array と書けて良さそうなものですが、そのような記法は残念ながらありません。

Ada 2020での改善

ASCIIコードが普及しきっていなかった時期に作られた古い言語ではISO 646の文字だけでソースコードが書けるようになっているものが多いです。 C言語のトライグラフなんかもそうです。 まあつまり、Adaではまだ使っていないASCII記号が沢山あります。 括弧も丸括弧しか使ってませんでした。

驚きです。 Ada 2020ではついに、ついに、これまで使われていなかった記号が使われるようになります。 95でも、2005でも、2012でも手が付けられることがなかった部分です。

Ada 2020で新たに使われるようになるのは @, [, ] の三文字です。 このうち [] がaggregate式に使われます。

そうです、配列のaggregate式に使われる括弧が ( ) から [ ] に変更されます。 驚きです。

勿論互換を確保する必要がありますので ( ) も従来通り使えますが、[ ] を用いた場合は先述のような優先順位のための括弧との区別を考える必要がなくなるわけです。 要素数1の場合でもpositional associationで書けますし、要素数0の場合は単に [] で良くなります。

declare
   A3 : Vector (1 .. 3) := [1.0, 2.0, 2.0];
   A4 : Vector (1 .. 3) := [1 => 1.0, 2 .. 3 => 2.0];
   A7 : Vector (1 .. 1) := [1.0];
   A8 : Vector (1 .. 1) := [1 => 1.0];
   AB : Vector (1 .. 0) := [];
   AC : Vector (1 .. 0) := [1 .. 0 => <>];
   AD : Vector (1 .. 0) := [others => <>];

pygmentsがエラーになってシンタックスハイライトされないですね。残当。

注意点として、recordのaggregate式は ( ) のままです。 ↓の関連AIのタイトルに Container aggregates が含まれますように、ユーザー定義型もaggregate式で初期化できるようになります。 そのため、record型そのものの生のaggregate式には ( )、ユーザー定義の初期化には [ ] と使い分けることになります。

関連AI

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

  • AI12-0306-1 Split null array aggregates from positional array aggregates

昨日の例を書き直し

ておきます。 これがあるから、順番としてこっちを先にするべきだったのですよ……。

declare
   Array_Var : array (1 .. Length) of T :=
     [for I in 1 .. Length => Create (I)];
declare
   Geometric : constant array (0 .. Length) of Integer :=
     [for I in 0 .. Length => 2 ** I];
declare
   Identity_Matrix : constant array (1 .. Length, 1 .. Length) of Float :=
     [for I in 1 .. Length => [I => 1.0, others => 0.0]];

所感

思い切りましたよねえ……という感想しか出てこないです。 uniformと言いつつ新しい文法と頭痛の種を追加したC++11のuniform initialization syntaxを連想させてくれます。

ユーザー定義型でaggregate式を使えるようにする方法はまた後日。 (ネタを使い切らないよう小出し)