Adaの日本語情報を増やす(5)

今回はフェイントでOOPについてまとめる、その1。ランデブーとか後回し。

AdaにおけるOOPは一応のところ「Ada95から導入された」ということになっている。とはいえ、Ada83の段階でもすでにOOPの素養――具体的には「派生型」を持っており――Ada95におけるOOP対応もこの派生型を拡張して行われた。

派生型が持つ強力な性質として、「派生元の型」に対して定義されている副プログラム(引数にその型を持ったprocedureや、返値にその型を持つfunctionなど)を自動的に継承する点が挙げられる。ただし、継承されるのはその「派生元の型」が定義されているパッケージ内で定義された副プログラムに限る。

口で説明するより次のサンプルコードを読んだ方が速いだろう。

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure OOP1 is
   package Drinks is
      type Drink is private; -- カプセル化のため内部構造は開かさない
      -- 同一パッケージに存在する以下の3つの副プログラムが派生先に継承される
      procedure Add(O : in out Drink; V : in Positive);
      procedure Spilt(O : in out Drink; V : in Positive);
      function Get_Volume(O : in Drink) return Natural;

   -- せっかくprivateなのに何でスペックに実定義を書くのか?
   -- コンパイラの都合上らしいですよ
   private
      type Drink is record
         Volume : Natural := 0;
      end record;
   end Drinks;
   
   -- スペック通りに実装します。
   package body Drinks is
      procedure Add(O : in out Drink; V : in Positive) is
      begin
         O.Volume := O.Volume + V;
      end Add;

      procedure Spilt(O : in out Drink; V : in Positive) is
      begin
         O.Volume := O.Volume - V;
      end Spilt;

      function Get_Volume(O : in Drink) return Natural is
      begin
         return O.Volume;
      end Get_Volume;
   end Drinks;

   -- 別パッケージで派生した場合
   package Coffees is
      -- 派生型の宣言だけする
      type Coffee is new Drinks.Drink;
   end Coffees;

   -- 主プログラム内で派生した場合
   type Beer is new Drinks.Drink;

   -- 実験用に変数を定義
   Cup_C : Coffees.Coffee; -- 当然パッケージ名から
   Cup_B : Beer;

begin
   -- 自分で実装していないのに、Drinks内の副プログラムがCoffeesに継承されている!
   Coffees.Add(Cup_C, 200); 
   Coffees.Spilt(Cup_C, 100);
   Put("Cup_C: "); Put(Coffees.Get_Volume(Cup_C)); New_Line;
   

   -- こちらもやはり継承されているのが分かる
   Add(Cup_B, 100);
   Spilt(Cup_B, 50);
   Put("Cup_B: "); Put(Get_Volume(Cup_B)); New_Line;

   -- Cup_C:         100
   -- Cup_B:          50
end OOP1;

Coffees.AddSpiltは定義されていないにもかかわらず、派生型のおかげで問題なく使用できるのが分かると思う。パッケージCoffeesには次のような副プログラムが自動的に定義されたと思えば良い。

procedure Add(O : in out Coffee; V : in Positive);
procedure Spilt(O : in out Coffee; V : in Positive);
function Get_Volume(O : in Coffee) return Natural;

なお、あくまでもCoffees.Cofee用の副プログラムがCoffees内に自動的で定義されるだけであり、派生元の副プログラムに派生先の型を許容する機能があるわけではない。つまり、次のコードはコンパイルエラーになる。

Drinks.Add(Cup_B, 100);

Drinks.Addが引数に取るのはあくまでもDrinks.Drinkであるので、Coffees.Coffee型であるCup_Bを操作したいのならば、Coffees.Addを呼び出す必要がある。この(Javaプログラマーが首をかしげそうな)点は、後述のClass Wide型と関連する重要な性質なので覚えておいて欲しい。

さて、本来派生型は型の安全性を高めるために存在するようだが(たとえば、間違ってBeerとVineを混ぜたりしないように)、今見ればOOPにおける継承に近い性質を持っているのが分かると思う。この点を踏まえつつ、次はAda95のtagged recordに続きます。