Ada 2020 (44日目) - その他の式関連の変更

終わらないその他シリーズ。

limited型とraise式

Ada 2020からは limited 型を求められている箇所でもraise式が使えるようになりました。

raise式はAda 2012 with Technical Corrigendum 1で導入されました。 しかし limited 型に使える式としては認められていませんでした。

package P1 is
   type T (<>) is limited private;
private
   type T is limited null record;
end P1;
declare
   Object : P1.T := raise Program_Error;

他にはAda 2020から追加されるdeclare式も limited 型に使えます。

関数とNo_Return

Ada 2020からは関数に No_Return アスペクト(またはpragma)が指定できるようになりました。

No_Return はAda 2012から導入されました。 しかしこれまでは手続きにしか指定できませんでした。

with P1;
package P2 is
   function Unimplemented return Boolean with No_Return;
   function Unimplemented return P1.T with No_Return;
end P2;

ところで関数にはreturn文を最低ひとつは書かないといけないルールがあります。 そのため従来このような関数の本体ではraise文に続けてダミーのreturn文が置かれてきました。

それも簡単な型なら良いですが複雑な型や P1.T のように抽象型ですとダミーの値を用意するのは大変です。 そのため手っ取り早く型だけ満たす手段として再帰も利用されてきました。

package body P2 is

   function Unimplemented return Boolean is
   begin
      raise Program_Error;
      return False;
   end Unimplemented;

   function Unimplemented return P1.T is
   begin
      raise Program_Error;
      return Unimplemented;
   end Unimplemented;

end P2;

仕様部からbody部のスケルトンを生成するツールであるgnatstubも再帰を使ったコードを吐きます。

しかし再帰は再帰で別の問題があったりします。

処理系にスタックの使用量を予測する機能があれば誤認されたりしますし、GNATの処理系定義の pragma Restrictions (No_Recursion); に引っかかったりもします。

これはreturn文の中でraise式を使えば再帰を使わずに済みます。

package body P2 is
   -- 省略

   function Unimplemented return P1.T is
   begin
      return raise Program_Error;
   end Unimplemented;

end P2;

更に言えば、わざわざ本体側を書かなくてもexpression functionにしてしまえば仕様部だけで済みます。

with P1;
package P2 is
   function Unimplemented return Boolean with No_Return is
     (raise Program_Error);
   function Unimplemented return P1.T with No_Return is
     (raise Program_Error);
end P2;

raise式を全ての型に使えるようになったことを覚えておきますと No_Return な関数を楽に書けます。

文字列の”&”

文字列の "&" 演算子がstaticになりました。

現状static関数で使える複合型(composite type)は文字列のみですので上手く使いたいですね。 Wide_Wide_String なら32ビット値の配列として使えますぜへへへ……。

悪巧みは置きまして、staticな文字列を要求される External_Name 等でも式を使えることになりますのできちんと活用できる場面もあると思われます。

universal_integerと範囲チェック

universal_integerは型が付く前の式を表す架空の型です。 基本的にはコンパイル時定数です。

declare
   U : constant := 0;

ところが一部の属性を使うと実行時の値に型が付かないままになることがあります。 つまりuniversal_integerにも実行時の値があります。

declare
   type T is mod 2 ** 8;
   X : Wide_Wide_Character;
   Y : T;
begin
   Ada.Wide_Wide_Text_IO.Get (X);
   Y := T'Mod (Wide_Wide_Character'Pos (X));

'Pos の結果の型は文脈上求められている整数型になります。 'Mod の引数の型は実際に渡された値の型になります。 このような属性を組み合わせますと型が付きませんので実行時の値としてのuniversal_integerを出現させることができます。

Ada 2020では実行時のuniversal_integerがroot_integerの範囲を超えた場合に Constraint_Error になると規定されました。 浮動小数点数も同じで実行時のuniversal_realがroot_realの範囲を超えたら Constraint_Error になります。

root_integerやroot_realは処理系が実行時に扱える最大の整数または浮動小数点数です。 型付けされた全ての整数型や浮動小数点数型はroot_integerやroot_realの派生型(部分型)です。

注意点としてコンパイル時は依然として別扱いです。

例えば64ビットの符号なし型を定義するときは次のように書きますよね。

type Unsigned_64 is mod 2 ** 64;

2 ** 64 は64ビットに収まっていませんが64ビットまでしか扱えない環境でもコンパイルできます。 この 2 ** 64 はコンパイル時universal_integerです。

つまりコンパイル時定数としてのuniversal_integerはroot_integerに縛られません(処理系依存の何らかの上限はあるはずです)が、実行時のuniversal_integerはroot_integerの範囲に縛られることになります。

関連AI

  • AI12-0172-1 Raise expressions in limited contexts

  • AI12-0201-1 Missing operations of static string types

  • AI12-0269-1 Aspect No_Return for functions reprise

  • AI12-0227-1 Evaluation of nonstatic universal expressions when no operators are involved