Ada 2020 (41日目) - その他のrecordの変更

あまりにも細かいやつは紹介しなくていいとは思うのですが、実際書き始めると自分でも案外発見があったりしますので一応見ていきます。

バリアントレコード

今までaggregate式でバリアントレコードの可変部分を書くときはdiscriminantはstaticである必要がありました。

type D_Type is (A, B, C);
type VR (D : D_Type) is
   record
      case D is
         when A | B =>
            Element_1: Float;
         when C =>
            Element_2 : Character;
      end case;
   end record;

function Create (D : D_Type) return VR is
  (case D is
     when A => VR'(D => A, Element_1 => 1.0),
     when B => VR'(D => B, Element_1 => 1.0),
     when C => VR'(D => C, Element_2 => 'C'));

discriminantを実行時の値で指定するならこんな風にする必要がありました。

function Create (D : D_Type) return VR is
begin
   return Result : VR (D => D) do
      case D is
         when A | B =>
            Result.Element_1 := 1.0;
         when C =>
            Result.Element_2 := 'C';
      end case;
   end return;
end Create;

つまり A の場合と B の場合をまとめようとすればひとつの式で書けませんでした。 ひとつのaggregate式で済まないということは要素の指定漏れがあってもチェックされなくなるということでもあります。

Ada 2020では可変部分を書くときもdiscriminantを実行時の値で指定できるようになりました。 discriminantが合わない場合は Constraint_Error になります。

function Create (D : D_Type) return VR is
  (case D is
     when A | B => VR'(D => D, Element_1 => 1.0),
     when C     => VR'(D => C, Element_2 => 'C'));

Unchecked_Unionとaggregate式

今まで Unchecked_Union のaggregate式にnamed associationを使えませんでした。

type U32 (D : Integer := 0) is
   record
      case D is
         when 0 =>
            As_Float : Float;
         when others =>
            As_Unsigned : Interfaces.Unsigned_32;
      end case;
   end record
     with Unchecked_Union;

X : U32 := (0, 1.0);
Y : U32 := (D => 1, As_Unsigned => 16#3f80_0000#); -- error

Ada 2020からは使えるようになりました。

pygmentsさん16進リテラルに _ 挟んだだけでエラーになるの情けないでしょう……。

Unchecked_Unionとin式

Unchecked_Union の比較では両辺がconstrained、つまり型でdiscriminantが固定されていなければなりません。 いずれかがunconstrainedの場合は Program_Error になります。

declare
   subtype Float_U32 is U32 (0);
begin
   if Float_U32 (X) = Float_U32 (Y) then
      null;
   end if;

でないと FloatInterfaces.Unsigned_32 のどちらで比較していいかわからなくなるからですね。

in式では値と型の関係のため基本的に全部 True になりますが、unconstrained in constrainedの場合だけ Program_Error になります。

begin
   if Float_U32 (X) in U32 then
      null;
   end if;
   if Float_U32 (X) in Float_U32 then
      null;
   end if;
   if X in U32 then
      null;
   end if;
   if X in Float_U32 then -- Program_Error
      null;
   end if;

X in Float_U32 を求めるには X.D を確定させる必要があるからでしょうか。 Ada 2005のときのAIを見てみましたが理由は簡単には読み取れそうにないです。

  • AI95-00216-01 Unchecked unions – variant records with no run-time discriminant

で、まあ、ここまで前置きです。 Ada 2020でin式はcase文のように複数の選択肢を続けて書けるようになりました。

begin
   if X in U32 | Float_U32 then
      null;
   end if;

この場合左から X in U32 が先に判定されて True になるのか、それとも Program_Error になるのか曖昧でした。 Ada 2020で Program_Error になると確定しました。

抽象型とin式

ユーザー定義の "=" 演算子を持つ抽象型で実体が基本型の場合、実体が可視の箇所からはin式が禁止されます。

package Pkg1 is
   type T is private;
   function "=" (Left, Right : T) return Boolean;
   function Is_Zero (X : in T) return Boolean;
private
   type T is range -100 .. 100;
end Pkg1;

package body Pkg1 is

   function "=" (Left, Right : T) return Boolean is
   begin
      return Integer (Left) = Integer (Right);
   end "=";

   function Is_Zero (X : in T) return Boolean;
   begin
      return X in 0; -- error
   end Is_Zero;

end Pkg1;

抽象型のin式は "=" が使われます。 対して基本型のin式はユーザー定義の演算子を介さずに直接比較されます。 両方の性質が可視の箇所ではエラーになるようになったというわけです。

Pkg1 の外で Pkg1.T にin式を使うことはでき "=" が使用されます。

ドット記法のrenames

renames でドット記法に別名を与えるときはオブジェクト名部分も renames 可能でなければならないルールが追加されました。

type T is tagged null record;
procedure Method (Object : in T) is null;

Object : T;
procedure Bound renames Object.Method;

この Object の部分が renames できないと駄目、と言いましてもドット記法が書けるのは tagged 型のみですから駄目なケースも限られます。 具体的にはバリアントレコードの可変部分では駄目ということです。

type V_Type (D : Boolean := False) is
   record
      case D is
         when False =>
            Element : T;
         when True =>
            null;
      end case;
   end record;

V : V_Type := (D => False, others => <>);
procedure Bound renames V.Element.Method; -- error

ユーザー定義indexingもこれに倣います。

上書き不可能なアスペクト

tagged 型に付くアスペクトには一度指定すると派生先で上書き不可能(nonoverridable)なものがあります。

  • Aggregate

  • Constant_Indexing

  • Default_Iterator

  • Implicit_Dereference

  • Iterator_Element

  • No_Controlled_Parts

  • Variable_Indexing

  • Integer_Literal

  • Real_Literal

  • String_Literal

(ざっと見ただけですのでまだあるかも知れません。)

No_Controlled_Parts 以外は全部コンテナ用の構文糖ですね。 つまりはアップキャストやダウンキャストで構文糖の展開を変えることはできません。

この上書き不可能なアスペクトにいくつかルールが追加されました。

Constant_Indexing/Variable_Indexing

Constant_IndexingVariable_Indexing は一度に指定しないといけなくなりました。 基底型で Variable_Indexing だけ指定しておいて派生先で Constant_Indexing を追加するようなことは禁止されました。

Objective-Cのように NSMutableArrayNSArray から派生させるようなことはやめた方がいいですね。

<読み飛ばし推奨> ちなみに変更可能不可能を継承階層で表現するなら、D言語のconstとimmutableに習って読み取り専用の抽象型から変更不可能と変更可能の両方を派生させるのが賢いやり方と思います。 他の参照を通じて変更されるかもしれないのと、本当に変更不可能なのとを区別できますので。 </読み飛ばし推奨>

interface

interface から継承した上書き不可能なアスペクトが衝突した場合エラーになると定められました。

type I1 is interface with Constant_Indexing => Constant_Reference;
type Reference_Type (Element : not null access Float) is null record
  with Implicit_Dereference => Element;
function Constant_Reference (Object : I1; Index : Positive)
  return Reference_Type is abstract;

type I2 is interface with Constant_Indexing => Element;
function Element (Object : I1; Index : Positive) return Float is
  abstract;

type T is new I1 and I2 with null record; -- error

この例では Constant_Indexing が衝突しています。

Adaの interface はEiffelやDelphiやC#のようにサブプログラム名の衝突を解決することもできませんし、こうして組み合わせが確実にエラーになる例もできてしまいましたしで使い辛いです。 やはり継承よりも generic 以下略。

関連AI

  • AI12-0086-1 Aggregates and variant parts

  • AI12-0160-1 Adding an indexing aspect to an indexable container type

  • AI12-0162-1 Memberships and Unchecked_Unions

  • AI12-0174-1 Aggregates of Unchecked_Unions using named notation

  • AI12-0204-1 Renaming of a prefixed view

  • AI12-0211-1 Interface types and inherited nonoverridable aspects

  • AI12-0328-1 Meaning of limited type and record type in 4.5.2(28.1/4)