Ada 2020 (20日目) - Globalその2

続き。 今回理解しきれているか自信がありません。 おかしいところがありましたらご指摘ください。

これまでのあらすじ

翻訳単位や可視性の壁を超えてグローバル変数へのアクセスが競合していないか検証するために Global アスペクトで表明ができるようになりました。

経緯

ところで、当たり前ですがこのin/outの解析は引数についても行われるわけです。 これも当たり前ですが in パラメータに渡した実引数は読み取り(in)が、out パラメータに渡したら書き込み(out)が行われていると見做されます。 in out パラメータはその両方です。

元々サブプログラムのシグネチャに inout が付いているため引数に関しては何もしなくても充分と思われるかもしれません。

しかしAdaでは in パラメータに書き込むこともできます。

例えば Ada.Numerics.Float_RandomAda.Numerics.Discrete_RandomRandom 関数は次の乱数を求める副作用として引数 Gen を更新します。

何の表明もなく引数を書き換えるなんて酷いですよね。 関数は副作用なしがPascal等古代言語の暗黙の規約です。 Fortranでも random_number はサブルーチンになっています。 Adaでも2012で解禁されるまでは関数は in のみのはずでした。 乱数ライブラリはAda 95からあります。

これは何も規格が裏技を要求しているわけではなく、Adaでは in 引数を書き換える方法がいくつかあります。 勿論キャストやメモリアクセス等で所謂危険な操作を行うこともできますが、認められた安全なやり方もあります。

問題

次のパッケージを実装してください。 Random 関数は呼び出しごとに異なる値を返す必要があります。 真面目に乱数を実装する必要はありません。 AddressExportImport 等と標準ライブラリを含めた他のパッケージ、グローバル変数は使用禁止とします。 access 型は使えるものとします。

package Float_Random is
   type Generator is limited private;
   function Random (Gen : Generator) return Float;
     -- 呼び出しごとに異なる値を返すこと
end Float_Random;

access 型は使えるものとします」って時点で答えですけどね!

実装例1

メモリリーク上等。

package Float_Random is
   type Generator is limited private;
   function Random (Gen : Generator) return Float;
private
   type Float_Access is access Float;
   type Generator is limited record
      State : not null Float_Access := new Float'(0.0);
   end record;
end Float_Random;
package body Float_Random is

   function Random (Gen : Generator) return Float is
   begin
      Gen.State.all := Gen.State.all + 1.0;
      return Gen.State.all;
   end Random;

end Float_Random;

標準ライブラリを禁止したのでこの回答例ではメモリリークさせていますが実際には Ada.Finalization.Limited_Controlled を使ってRAIIで解放できます。

実装例2

Coextension。

package Float_Random is
   type Generator is limited private;
   function Random (Gen : Generator) return Float;
private
   type Generator (State : not null access Float := new Float'(0.0)) is
     limited null record;
end Float_Random;

-- bodyは上に同じ

上の改良版です。 匿名の access 型のdiscriminantに割り当ての式(new)を書いた場合割り当ては一体化されます。 つまりRAIIを使わなくてもメモリリークしません。

……規格上では。 残念ながらGNATにはこの機能は実装されていません。

実装例3

GNATでも通るやり方です。 呼ばれ方はvariable view等です。

package Float_Random is
   type Generator is limited private;
   function Random (Gen : Generator) return Float;
private
   type Generator is limited record
      Variable : not null access Generator := Generator'Unchecked_Access;
      State : Float := 0.0;
   end record;
end Float_Random;
package body Float_Random is

   function Random (Gen : Generator) return Float is
   begin
      Gen.Variable.State := Gen.Variable.State + 1.0;
      return Gen.Variable.State;
   end Random;

end Float_Random;

最初に自分自身への書き込み可能な access 型の値を獲得しておくことで可視性の上では変更が許されていない箇所からでも書き換えができます。

in パラメータや acces constant を経由して可視性の上で定数として見えている箇所からも書き換えられるというだけで、実体を定数として宣言しようとするとエラーになります。 以前は実体を定数として宣言することも許されていたのですがAda 2012 Technical Corrigendum 1で禁止されました。 ですのでコンパイラによっては現時点でも変化する定数を作れます。

引数アクセスの表明

抽象型の場合は access 経由の書き換えとしても同じ変数の状態と見做したいですし、最後のやり方では間違いなく in パラメータに渡された変数を書き換えています。

つまり引数の in out は嘘の場合があります。

ですので検証のためには本当はどうアクセスしているかを表明する必要があります。

Globalアスペクト(引数)

ここでも Global アスペクトを使用します。 (引数なのに。 引数なのに。)

overriding を使って引数の in out を上書きできます。 (またまた予約語を再利用している……。)

package Float_Random is
   type Generator is limited private;
   function Random (Gen : Generator) return Float
     with Global => overriding in out Gen;
private
   type Generator is limited record
      Variable : not null access Generator := Generator'Unchecked_Access;
      State : Float := 0.0;
   end record;
end Float_Random;

これは回答例3に Global アスペクトを書いた例です。 回答例2も(自信がありませんが恐らく)これでいいです。 しかし、実は回答例1に適用するにはこれだけでは駄目です。

Internalアスペクト/Compoundアスペクト

多くの言語では都合よく忘れられていることがあります。 ヒープというのはグローバル変数なのです。 ヒープをスレッドローカルなものとしてスレッド間のポインタ転送を禁止している言語もあります。

Adaでもこの性質は忘れられておらず様々な制限となっています。 例えば Pure なパッケージでは動的にメモリ割り当て可能な access 型を宣言できません。

SPARKでは acces 型のownership検証が実装されました。 Rust等で流行りのやつです。 (https://www.adacore.com/devlog にて “Mon May 13 2019” で検索すると出てきます。 来年予定のリリースノートは http://docs.adacore.com/live/wave/spark2014-release-notes/html/spark2014_release_note/release_notes_20.html にあります。 それ以前にもμSPARK等が研究用として存在していました。)

Ada 2020でも最初はownership検証が検討されていましたが、機能を制限しているSPARKと異なりフルセットのAdaに持ち込むには余りにも難易度が高い話です。 そのため、まずは簡易的な access 型の所有関係だけが採択されました。

Internal アスペクトはその access 型が他の型の部品として使われることを示します。 Compound アスペクトは Internalaccess 型を所有することを示します。 Internalaccess 型を通じた変更は外側の Compound 型オブジェクトの変更と一体として扱われます。 木構造等入れ子の場合は Compound はルートにのみ付けます。

回答例1に付けてみます。

package Float_Random is
   type Generator is limited private;
   function Random (Gen : Generator) return Float
     with Global => overriding in out Gen;
private
   type Float_Access is access Float
     with Internal;
   type Generator is limited record
      State : not null Float_Access := new Float'(0.0);
   end record
     with Compound;
end Float_Random;

‘Move

Internalaccess 型は複数の変数が同じヒープオブジェクトを指すことを禁止されます。 所有者は1つでなければならないからです。

しかし、複数から所有させる意図がなくても一時的にでも代入ができないと困るときがあります。 例えば単純なリンクリストへの追加でも困ります。

declare
   type Node;
   type Node_Ptr is access Node with Internal;
   type Node is limited record
      Element : Float;
      Next : Node_Ptr;
   end record;
   type List is limited record
      First : Node_Ptr;
   end record
     with Compound;
   Root : List := (First => null);
begin
   -- 1つ目を追加
   Root.First := new Node (Element => 1.0, Next => null);
   -- 2つ目を追加
   declare
      New_Node : not null Node_Ptr :=
        new Node
          (Element => 2.0,
           Next    => Root.First); -- error
   begin
      Root.First := New_Node; -- error
   end;

追加の過程で一時的に2つの変数が同じヒープオブジェクトを指すことになります。

このため access 型のオブジェクトに 'Move 属性が追加されました。 オブジェクトの値を返すと同時にそのオブジェクトを null にします。

begin
   -- 1つ目を追加
   Root.First := new Node (Element => 1.0, Next => null);
   -- 2つ目を追加
   declare
      New_Node : not null Node_Ptr :=
        new Node
          (Element => 2.0,
           Next    => Root.First'Move);
   begin
      Root.First := New_Node'Move;
   end;

自信はありませんが恐らく同じ Compound オブジェクト内でしたら複数から同じヒープオブジェクトを指すこともできるのではと思います。 でなければ双方向リンクリストが実装できなくなります。

匿名の access 型への代入(変換)も許されるのではと思います。 でなければユーザー定義参照型も実装できずコンテナに支障がありますので。

関連AI

  • AI12-0240-6 Global aspect and access types used to implement Abstract Data Types