オブジェクト指向


ネームスペース(NameSpace)の定義


 soopyには実はクラスという概念がありません。
でも、オブジェクト(インスタンス)は作れます。
soopyでは、オブジェクトはネームスペースと
呼ばれます。ネームスペースは、別の呼び方をすると
連想配列ともいいます。

 ネームスペースを作るには、{ と }に囲まれ、セミコロン(;)
で区切られたフィーチャの宣言の並びです。
 フィーチャの宣言は

 <シンボル>: 文
 の形をしているか、
または、
 関数宣言
または、
 定数宣言
または、
 プロパティ宣言
または、
 データタイプ宣言
です。


 下の文は、オブジェクトを生成します。
(しかし、オブジェクトそのものではありません。)

      { a: 3; b: x; }


あくまで、オブジェクトを生成する文なので、
上の文が実行されるたび、生成されるオブジェクトは
別物です。

 よって、

      fun f(){
        do: [
          {
            a: 7;
            fun printIt(){do: [println a]; };
          }
        ];
      };


のとき、

      x = f();
      y = f();


とすると、xとyの指すオブジェクトは別物です。しかし、
xとyの構造は同じです。
 ということは、クラスを使わなくても、同じ性質の
オブジェクトを生成可能ということです。また、関数fは
C++などの普通のオブジェクト指向言語における コンストラクタの役目をしていることがわかると思います。

 fが引数をとる関数だとしたら、どうでしょう。
さまざまなオブジェクトが作れるのではないでしょうか。
やってみましょう。


  fun map(f,l){
    do: match(l){
          []:    [];
          x::xs: f x :: map(f,xs);
        };
  };

  fun con(list,func){
    do: [
      {
        fun run(){
          do: [
            map (func, list);
          ];
        };
      };
    ];
  };

  fun add1(x){
    do: [x + 1];
  };

  fun add2(x){
    do: [x + 2];
  };

  a = con([1,2,3,4,5], add1);
  b = con([1..5], add2);
  a run();
  b run();






フィーチャーのアクセス


   オブジェクトのフィーチャー(要素)を読み出すには
  オブジェクトに読み出したいフィーチャーの名前(シンボル)を   メッセージとして送ります。

  例。

      obj = {
        num:3;
        str:"Hello";
        fun func(x){do: [x*num]; };
      };
      obj num;  /* 3が返ってくる */
      obj str;  /* "Hello"が返ってくる */
      obj func; /* 引数に3を足す関数が返ってくる */
      f = obj func;
      f(7);     /* 10が返ってくる */




フィーチャーの更新


   オブジェクトのフィーチャーに書き込む(更新)するには、
  当たり前ですが、代入文を使います。
   フィーチャーを読み込むのは

   <オブジェクト> <フィーチャー名>
  ですから、書き込みは当然、

   <オブジェクト> <フィーチャー名> = <書き込みたい値>
  です。簡単ですね。
  (Lispを知っている方には、Common Lisp の setf を考えていただければ
いいと思います。かえって意味不明?)

  例。

      obj = {
        num:3;
        str:"Hello";
        fun func(x){do: [x*num]; };
      };
      obj num;      /* 3が返ってくる */
      obj num = 8;  /* num の値を更新 */
      obj num;      /* 8が返ってくる */




プロパティ宣言


  プロパティは、Delphiとかにあるものと考え方は同じです。

  例。

a = {
        b: 1;

        fun getf(){do: b;};
        fun setf(x){do: b = x;};

        property c {
            set: setf;
            get: getf;
        };
    };

  上の例で、

  a c;

  とフィーチャーcにアクセスしようとすると、
 set: で指定された関数が呼び出され、その返値が
 値となります。

  また、
  a c = value;

  とフィーチャーcに代入しようとすると、
 get: で指定された関数が、valueを引数として
 呼び出されます。



データタイプ宣言


 データタイプ(代数型)は、単純なものは、C/C++言語などの
列挙型に似ています。

  例。

  datatype day = Mon | Tue | Wed | Thi | Fri | Sat | Sun;


 このままでは、せいぜい定数の代わりぐらいにしかなりませんので
ちょっと拡張してみましょう。
datatype day =
  Mon {
    const workday? = true;
  }
| Tue {
    const workday? = true;
  }
| Wed {
    const workday? = true;
  }
| Thi {
    const workday? = true;
  }
| Fri {
    const workday? = true;
  }
| Sat {
    const workday? = false;
  }
| Sun {
    const workday? = false;
  }
;

 とすると、
  Mon workday?;  # true

  Sun workday?;  # false


 のように使えます。

 では、workday?の逆holiday?を定義してみましょう。
  Mon {
    const workday? = true;
    const holiday? = false;
  }
| Tue {
    const workday? = true;
    const holiday? = false;
  }
  ...

 のように、すべての曜日にholiday?を定義してもいいのですが、
面倒です。そこで、
datatype day {
    property holiday? {get: isHoliday;};
    fun isHoliday(){
      do: [!workday?];
    };
  }
= Mon {
    const workday? = true;
  }
| Tue {
    const workday? = true;
  }
| Wed {
    const workday? = true;
  }
| Thi {
    const workday? = true;
  }
| Fri {
    const workday? = true;
  }
| Sat {
    const workday? = false;
  }
| Sun {
    const workday? = false;
  }
;

 のように、datatype day の後ろにデータタイプの
すべてのメンバーから使用できるフィーチャーを 定義します。
 こうすれば、
Mon holiday?;
Sat holiday?;

 というふうに、day型のすべての要素で、メソッドholiday?
が使用できます。


 このように、データタイプはあたかもオブジェクトのように
さまざまなフィーチャーをもてます。フィーチャーには
定数、関数、変数などが使用可能です。

 また、データタイプは初期化値をもてます。
datatype fig =  Rect(x:int, y:int) | Circle(r:int);

 とすることにより、
r = Rect(5,7);
c = Circle(8);

r x;  # 5
c r;  # 8

 のように、初期化した値にアクセス出来ます。
さらに、
datatype fig =
  Rect(x:int, y:int){
    fun area(){
      do: x * y;
    };
  }
| Circle(r:int){
    pi: 3;
    fun area(){
      do: r * r * pi;
    };
  }
;

 とすると、
r = Rect(5,7);
c = Circle(8);

r area();  # rはRectなので、Rectのareaが呼ばれる。値は、35
c area();  # cはCircleなので、Circleのareaが呼ばれる。値は、192

 のように使えます。

 このように、Soopyのデータタイプは、StandardML や, Gofer などの
関数型言語の代数型を拡張したものになっています。



継承


 soopyは継承をサポートしていません。
 ただし、継承に変わる機能として、ネームスペース同士の
加算をサポートしています。

 例えば、

  super_obj = {
    name: "ansi common lisp";
    writer: "paul";
  };
 のときに

  child_obj = super_obj + {
    publisher: "Pearson";
  };

 とすることにより、child_obj は、super_obj の要素の
ほかに、新たな要素を追加したオブジェクトになります。

 また、
  another_obj = child_obj + {
    name: "hack!";
  };

 とすると、同名の要素(name)は隠されてしまい、
アクセス出来なくなります。
 これを回避するためには、もとのオブジェクトの要素名を 下記のようにリネームします。('$'で始まるものをシンボル)
  yet_another_obj = child_obj rename {name: $rename_name;} + {
    name: "hack?";
  };



また、オブジェクトのコピーを作るときにも、加算が使えます。
前述のように関数を使ってもいいのですが、もっと簡単に
元になるネームスペースに空のネームスペースを足してやればいいのです。
  original_rect = {x:0; y:0; width:100; height:100;};]

  rect1 = original_rect + {};

コピーされたオブジェクトに変更を加えても、元のオブジェクトの値は
変わりません。

さらに、ちょっとだけ違うオブジェクトを作るときには、
変えたい要素だけのネームスペースを足してやります。
  rect2 = original_rect + {width: 640; height:480;};





ネームスペースから関数を作る。


 ネームスペースにメッセージevalを送ることにより、
関数をつくることが出来ます。書式は、関数定義にほぼ
同じです。ただ、新たに、arg: という要素を指定できます。
 arg: は引数のリストです。

 例。

  f = {arg:[x]; do:[x * x]; } eval;

 これで、二乗を求める関数を定義できます。