Ada 2020 (16日目) - static function

12月は31日までありますので折り返しは今日でした。 目玉を持ってくるならここですよね。 (意味不明。)

static

staticという単語は様々な使われ方がされています。 Adaでは「コンパイル時に決まる」静的な値のことです。

決して可視性の意味でも寿命の意味でもクラスメソッドの意味でもRTTI不要という意味でもリンク時の何かでもありません。

規格だけで使われていた単語だったのですが、Ada 2012で Static_Predicate アスペクトが追加されたためソースコードにも出現するようになりました。 これは静的に検証可能な述語を持つrefinement typeを作るためのもので、動的にしか検証できない Dynamic_Predicate と対になっています。 Static_Predicate の方はcase文等でソースコード上に全パターン書かないといけない場合等に反映されます。 (Dynamic_Predicate でもコンパイル時にチェック可能なケースでかつコンパイラの機嫌が良ければ警告ぐらいは出してもらえるかもしれません。 GNATには独自拡張で文脈から静的か動的かをコンパイラが判断する Predicate もあります。)

<読み飛ばし推奨> 「predicate type」と書いてしまいますとCecil等にあるアレの方が相応しいらしいです。 ですのでここではrefinement typeと書いています。 </読み飛ばし推奨>

これに限らず規格にとっての静的とコンパイラにとっての静的というのは別な話です。 実際のコンパイラは規格で決まっているよりも多くのことを静的に解決できますが、規格上staticな値が要求されている箇所に規格上静的ではないとされる式があったらエラーにしないといけません。 (C言語でもインライン関数の結果を enum の値にできたりしないのと同じです。)

経緯

Adaとは非常に動的な言語です。 (あくまで静的な型を持つ中では、です。)

定数、範囲型の範囲、固定長配列の要素数、 generic の引数、等々、広範に渡って実行時の値を用いることができます。 そのためメタプログラミングが他ほどは必要ありません。

それでもstaticな値が必要な箇所が少しはあります。 主にセマンティクスや内部表現に関わる部分です。 'Enum_Rep が必要な理由も 'Size 属性を動的に変えられないからでした。

staticな値を作るには関数は使えず式だけで書いてしまうしかありませんでした。

そのためAda 2012でif式が入るまでは Boolean'Pos (Condition) * True_Value + Boolean'Pos (not Condition) * Else_Value 等といった冗長なコードも時には書かれていました。 最近のAdaは式中で分岐やループもできますのでこのようなコードは必要ないです。 それでも Ada.Numerics.Complex_Types.Complex_Type の演算子も呼べないため複素数の足し算ひとつ取っても実数部と虚数部を別々に足さなければなりません。

冗長なだけで済めばまだしも関数が呼べないと不可能なこともあります。

特に抽象型の値を使うことは全くできませんでした。 抽象型はパッケージの外からはサブプログラムを通じてしか操作できないようになっています。 大事な特徴なのですが、それ故にstaticには扱えません。

そしてアドレスを表す型の System.Address は抽象型でした。 つまりコンパイル時にアドレス演算ができずpreelaboratedなパッケージ(実行時に追加の初期化を必要としない翻訳単位)でメモリマップドI/Oに用いるアドレスを定数として定義できませんでした。 これはAdaの用途から致命的でしたので各処理系で様々な拡張がなされてきました。

GNATにも処理系定義でpragma Allow_Integer_Address'To_Address 属性、 'Deref 属性等が用意されています。

Janus/Adaでは System.Address は抽象型ではないため生の整数値を渡せるようです。

Ada 2020での改善

Staticアスペクト

Static アスペクトを指定したexpression functionは引数もstaticであればコンパイル時の展開が保証されstaticな値を作るために使えるようになりました。

function Add (X, Y : Integer) return Integer with Static is (X + Y);

制限もいくつかあります。

  • expression functionのみです。 begin end を使用した普通の関数はstaticにはできません。

  • bodyにはなれません。 公開部に実装も書かないといけないため抽象型の中身には触れないことになります。

  • 再帰呼び出しはできません。

  • 引数のモードは in のみです。

  • 引数と返値の型はstatic subtype(数値型や列挙型等のスカラー型または文字列型)でなければなりません。 実行時の値を使った範囲等の制約や Dynamic_Predicate は持てません。

  • 事前条件や事後条件は書けません。

  • 返値の型が Type_Invariant を持っていては駄目です。

  • 当たり前ですが非staticな式は使えません。

結局のところ現段階では制限のため抽象型 System.Address の操作をstaticにすることはできませんでした。

そのためワークアラウンド的に Ada.Unchecked_ConversionSystem.Address_To_Access_ConversionsSystem.Storage_Elements の各関数は特別扱いされてpreelaboratedなパッケージでアドレス定数を定義できるようになりました。 需要の高さが伺えます。

制限はいずれ緩められていくことでしょう。 きっと。 多分。 いや知りませんけど。

関連AI

所感

いずれは複素数やアドレスの演算が完全に静的になることでしょう。

…。

……。

はい、はい、おっしゃりたいことはわかります。

とんでもない変更ですよね! これ!

現段階では再帰ができませんし使える型も限られていますが紛れもなく constexpr です。

コンパイル時プログラミングの道が開けたぜヒャッハー!!