Ada 2020 (25日目) - 契約と高階関数

普通の日。

経緯

契約の静的解析のためには高階関数の仮引数の側にも事前条件や事後条件を書けるようにする必要があります。

例えば、引数で渡された関数に固定の引数を渡すパッケージがあるとします。

package Pkg1 is
   type Divide_Access is access function (X, Y : Float) return Float;
   function Invoke (Divide : not null Divide_Access) return Float;
end Pkg1;
package body Pkg1 is

   function Invoke (Divide : not null Divide_Access) return Float is
   begin
      return Divide (0.0, 0.0);
   end Invoke;

end Pkg1;
generic
   with function Divide (X, Y : Float) return Float;
package Pkg2 is
   function Invoke return Float;
end Pkg2;
package body Pkg2 is

   function Invoke return Float is
   begin
      return Divide (0.0, 0.0);
   end Invoke;

end Pkg2;

これらのパッケージの引数 Divide に対して次のような 0.0 ではない Y を要求する Divide の実装が渡された場合、当然ですが実行時エラーになります。

function Divide (X, Y : Float) return Float
  with Pre => Y /= 0.0;
function Divide (X, Y : Float) return Float is
begin
   return X / Y;
end Divide;
with Pkg_1;
with Divide;
procedure Main is
   package Pkg2_Instance is new Pkg2 (Divide);
   Z : Float;
begin
   Z := Pkg1.Invoke (Divide'Access); -- runtime error
   Z := Pkg2_Instance.Invoke;        -- runtime error
end Main;

しかし検証器は Pkg1Pkg2 それぞれを別々に解析している分にはこれを検出できません。 というよりは別々に解析している限りは検出できないのが正しいです。 Divide というそれらしい名前を付けていますが Y => 0.0 を渡しても構わない実装を与えることもできますし。 しかし意図としましては割り算ですので Y => 0.0 はエラーとして検出したいわけです。

これまでは仮引数に事前条件や事後条件を書けませんでしたのでこのような意図を伝えることができませんでした。

Ada 2020での改善

access 型や generic の仮引数に事前条件と事後条件を書けるようになりました。

package Pkg1 is
   type Divide_Access is access function (X, Y : Float) return Float
     with Pre => Y /= 0.0;
   function Invoke (Divide : not null Divide_Access) return Float;
end Pkg1;
generic
   with function Divide (X, Y : Float) return Float
     with Pre => Y /= 0.0;
package Pkg2 is
   function Invoke return Float;
end Pkg2;

注意としまして、実引数との条件の矛盾を調べるのは検証器の仕事となります。 あくまでAdaの範囲では事前条件と事後条件は動的なチェックですので条件自体の矛盾のチェックも行われません。

実行時には仮引数と実引数の両方の条件が評価されます。 議論の上ではラッパーとして例えられました。

このようなイメージでしょうか。

-- This is an image!
package body Pkg1 is

   function Invoke (Divide : not null Divide_Access) return Float is
      function Divide_Wrapper (X, Y : Float) return Float
        with Pre =>Y /= 0 is
      begin
         return Divide (X, Y);
      end Divide_Wrapper;
   begin
      return Divide_Wrapper (0.0, 0.0);
   end Invoke;

end Pkg1;
-- This is an image!
package body Pkg2 is

   function Invoke return Float is
      function Divide_Wrapper (X, Y : Float) return Float
        with Pre => Y /= 0 is
      begin
         return Divide (X, Y);
      end Divide_Wrapper;
   begin
      return Divide_Wrapper (0.0, 0.0);
   end Invoke;

end Pkg2;

デフォルト初期化の事後条件である Default_Initial_Conditiongeneric の仮引数側の型に書くことができるようになっています。

関連AI