soopyには実はクラスという概念がありません。
でも、オブジェクト(インスタンス)は作れます。
soopyでは、オブジェクトはネームスペースと
呼ばれます。ネームスペースは、別の呼び方をすると
連想配列ともいいます。
ネームスペースを作るには、{ と }に囲まれ、セミコロン(;)
で区切られたフィーチャの宣言の並びです。
フィーチャの宣言は
<シンボル>: 文
の形をしているか、
または、
関数宣言
または、
定数宣言
または、
プロパティ宣言
または、
データタイプ宣言
です。
下の文は、オブジェクトを生成します。
(しかし、オブジェクトそのものではありません。)
{ a: 3; b: x; }
fun f(){ do: [ { a: 7; fun printIt(){do: [println a]; }; } ]; };
x = f(); y = 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;
a c = 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
Mon { const workday? = true; const holiday? = false; } | Tue { const workday? = true; const holiday? = false; } ...
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; } ;
Mon holiday?; Sat 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は継承をサポートしていません。
ただし、継承に変わる機能として、ネームスペース同士の
加算をサポートしています。
例えば、
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;
これで、二乗を求める関数を定義できます。