Ada 202x (20日目) - Globalその2 =============================== .. post:: Dec 20, 2019 :tags: ada, ada_2022 続き。 今回理解しきれているか自信がありません。 おかしいところがありましたらご指摘ください。 これまでのあらすじ ------------------ 翻訳単位や可視性の壁を超えてグローバル変数へのアクセスが競合していないか検証するために ``Global`` アスペクトで表明ができるようになりました。 経緯 ---- ところで、当たり前ですがこのin/outの解析は引数についても行われるわけです。 これも当たり前ですが ``in`` パラメータに渡した実引数は読み取り(in)が、``out`` パラメータに渡したら書き込み(out)が行われていると見做されます。 ``in out`` パラメータはその両方です。 元々サブプログラムのシグネチャに ``in`` と ``out`` が付いているため引数に関しては何もしなくても充分と思われるかもしれません。 しかしAdaでは ``in`` パラメータに書き込むこともできます。 例えば ``Ada.Numerics.Float_Random`` や ``Ada.Numerics.Discrete_Random`` の ``Random`` 関数は次の乱数を求める副作用として引数 ``Gen`` を更新します。 | http://www.ada-auth.org/standards/2xrm/html/RM-A-5-2.html#p8 | http://www.ada-auth.org/standards/2xrm/html/RM-A-5-2.html#p20 何の表明もなく引数を書き換えるなんて酷いですよね。 関数は副作用なしがPascal等古代言語の暗黙の規約です。 Fortranでも ``random_number`` はサブルーチンになっています。 Adaでも2012で解禁されるまでは関数は ``in`` のみのはずでした。 乱数ライブラリはAda 95からあります。 これは何も規格が裏技を要求しているわけではなく、Adaでは ``in`` 引数を書き換える方法がいくつかあります。 勿論キャストやメモリアクセス等で所謂危険な操作を行うこともできますが、認められた安全なやり方もあります。 問題 ++++ 次のパッケージを実装してください。 ``Random`` 関数は呼び出しごとに異なる値を返す必要があります。 真面目に乱数を実装する必要はありません。 ``Address``、``Export``、``Import`` 等と標準ライブラリを含めた他のパッケージ、グローバル変数は使用禁止とします。 access型は使えるものとします。 .. code-block:: ada package Float_Random is type Generator is limited private; function Random (Gen : Generator) return Float; -- 呼び出しごとに異なる値を返すこと end Float_Random; 「access型は使えるものとします」って時点で答えですけどね! 実装例1 +++++++ メモリリーク上等。 .. code-block:: ada 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; .. code-block:: ada 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。 .. code-block:: ada 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等です。 .. code-block:: ada 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; .. code-block:: ada 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`` パラメータや ``access constant`` を経由して可視性の上で定数として見えている箇所からも書き換えられるというだけで、実体を定数として宣言しようとするとエラーになります。 以前は実体を定数として宣言することも許されていたのですがAda 2012 Technical Corrigendum 1で禁止されました。 ですのでコンパイラによっては現時点でも変化する定数を作れます。 引数アクセスの表明 ------------------ 抽象型の場合は ``access`` 経由の書き換えとしても同じ変数の状態と見做したいですし、最後のやり方では間違いなく ``in`` パラメータに渡された変数を書き換えています。 つまり引数の ``in`` ``out`` は嘘の場合があります。 ですので検証のためには本当はどうアクセスしているかを表明する必要があります。 Globalアスペクト(引数) ++++++++++++++++++++++ ここでも ``Global`` アスペクトを使用します。 (引数なのに。 引数なのに。) ``overriding`` を使って引数の ``in`` ``out`` を上書きできます。 (またまた予約語を再利用している……。) .. code-block:: ada 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`` アスペクトを書いた例です。 .. container:: strike 回答例2も(自信がありませんが恐らく)これでいいです。 しかし、実は回答例1に適用するにはこれだけでは駄目です。 Internalアスペクト/Compoundアスペクト +++++++++++++++++++++++++++++++++++++ .. container:: strike 多くの言語では都合よく忘れられていることがあります。 ヒープというのはグローバル変数なのです。 ヒープをスレッドローカルなものとしてスレッド間のポインタ転送を禁止している言語もあります。 Adaでもこの性質は忘れられておらず様々な制限となっています。 例えば ``Pure`` なパッケージでは動的にメモリ割り当て可能な ``access`` 型を宣言できません。 SPARKでは ``access`` 型の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 202xでも最初はownership検証が検討されていましたが、機能を制限しているSPARKと異なりフルセットのAdaに持ち込むには余りにも難易度が高い話です。 そのため、まずは簡易な ``access`` 型の所有関係だけが採択されました。 ``Internal`` アスペクトはその ``access`` 型が他の型の部品として使われることを示します。 ``Compound`` アスペクトは ``Internal`` な ``access`` 型を所有することを示します。 ``Internal`` な ``access`` 型を通じた変更は外側の ``Compound`` 型オブジェクトの変更と一体として扱われます。 木構造等入れ子の場合は ``Compound`` はルートにのみ付けます。 回答例1に付けてみます。 .. code-block:: ada 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 +++++ .. container:: strike ``Internal`` なaccess型は複数の変数が同じヒープオブジェクトを指すことを禁止されます。 所有者はひとつでなければならないからです。 しかし、複数から所有させる意図がなくても一時的にでも代入ができないと困るときがあります。 例えば単純なリンクリストへの追加でも困ります。 .. code-block:: ada 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`` にします。 .. code-block:: ada 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 ------ .. container:: strike - `AI12-0240-6`_ **Global aspect and access types used to implement Abstract Data Types** .. _`AI12-0240-6`: http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ai12s/ai12-0240-6.txt