object = closure
mieのユニークかもしれない特徴として、
オブジェクトとクロージャが同じである、という点があります。
オブジェクトとクロージャは等価だ、という話は聞いたことがあるかもしれません。
Schemeのオブジェクトシステムなんかはクロージャでオブジェクトを作っているんでしたっけ。
逆にクロージャをオブジェクトで表現することもできます。
これはRubyのProcオブジェクトとかで、処理をオブジェクトとして受け渡しできるようになります。
でもでも、等価だと言いつつ違うじゃんか、と思いませんか。
等価なんだったらオブジェクト、クロージャ、というのは呼び方の違いだけで、
言語的には全く同じものであっても良いのではないかと思うのです。
mieのオブジェクトには波括弧と角括弧の2つの書き方がありますが、
同じものであるということを強調するため、今回は波括弧だけを用いることにします。
オブジェクトの要素部にはデータが入り、挙動部には処理が入ります。
{ 要素部 | 挙動部 }
例えば、
{ x | x * x }
と書けばxというデータを要素部として持ち、
xを2乗するという処理を挙動部に持つオブジェクトになります。
このオブジェクトはxを2乗する関数であると言えます。
このままではxは具体的な値を指していないので、
実際に使う時にはメタ結合によって値を束縛します。
{ x | x * x } <+ {2|};
2つのオブジェクトを結合すると、新しくオブジェクトが生成されます。
そのオブジェクトは次のようになります。
{ x = 2 | x * x }
未束縛状態の変数xに、整数2が束縛されます。
ここで生成されたオブジェクトは実行可能になりました。
実行してみます。
{ x = 2 | x * x } !;
--> 4
とここまでは前回の復習です。
さて、2乗する関数の例はどうみても関数であって、あれをオブジェクトとしては見れないかもしれません。
それではオブジェクトっぽい例を出してみます。
mieの行コメントは--で始めます。(Haskellと同じ)
具体的なオブジェクトを直接書く場合。
-- 太郎君の身長は150cm、体重は50kgです。
taro = { height = 150, weight = 50 |};
クラスのような抽象的なオブジェクトpersonを用意する場合。
-- 人間には身長と体重があります。
person = { height, weight |};
-- 太郎君は人間で、身長は150cm、体重は50kgです。
taro = person <+ {height = 150, weight = 50|};
personのheightとweightは未束縛であるため、
オーバーライドする要素は順番に書く分には名前を省略することができます。
taro = person <+ {150, 50|};
次のようにメタ結合を2回に分けて記述することもできます。
この場合も要素名は省略可能です。
-- 太郎君は人間で、身長は150cmで、体重は50kgです。
taro = person <+ {height = 150|} <+ {weight = 50|};
taro = person <+ {150|} <+ {50|};
ここまではデータ構造だけを定義してきました。
次に処理を記述します。
BMI指数を計算する関数を作り、太郎君のBMI指数を求めてみましょう。
ちなみにBMI指数とは肥満度を計る一つの尺度です。
-- BMI指数は、身長と体重を引数に取り、体重を身長の2乗で割った値を計算する関数です。
bmi = {height, weight| weight / (height / 100) ** 2};
-- BMI指数を、太郎君の身長と、太郎君の体重から計算します。
bmi {taro.height, taro.weight|};
--> 22.2
上の例は関数(bmi)とデータ(taro)がはっきりと分かれています。
しかし定義を見ると2つは同じように表現されています。
taro = { height = 150, weight = 50 |};
bmi = {height, weight| weight / (height / 100) ** 2};
これがオブジェクトとクロージャは同じであるということです。
さて、せっかくオブジェクト指向な言語を使っているのですから、
関数bmiはオブジェクトのメソッドにしたくなりますよね。
personオブジェクトのメソッドにしてみます。
heightとweightはpersonの要素を使うので、bmiメソッドの引数としては不要になります。
よってbmiメソッドは要素部が空なオブジェクト、つまり処理しか持たないオブジェクトになります。
-- 人間には身長、体重、BMI指数があります。
-- BMI指数は体重を身長の2乗で割った値です。
person = {
height, weight,
bmi = {| weight / (height / 100) ** 2}
|};
一般的な言語ではheight,weightをフィールドと呼び、bmiをメソッドと呼びますが、
mieにおいてフィールド、メソッドの区別は、
どう利用するかというプログラマ視点の役割でしかなく、
その実体は言語的には同じです。
bmiメソッドを追加したpersonを継承し、taroを再定義してみます。
-- 太郎君は人間で、身長は150cm、体重は50kgです。
taro = person <+ {height = 150, weight = 50|};
-- 太郎君のBMI指数を計算します。
taro.bmi[];
--> 22.2
ところで、personオブジェクトには挙動部がありません。
なんとなく勿体ないから、ここにbmiメソッドの処理を記述してみましょう。
人間の価値とはBMI指数で決まるのだよ! と思うなら、
次のようなオブジェクト設計にしても良いということです。
# よいこのみんなはまねしないでね☆
person = {
height, weight
| weigth / (height ** 2)};
-- 身長160cm、体重90kgの人間を計算します。
person [160, 90];
--> 35.2
今作ったpersonオブジェクトですが、先程作ったbmi関数と同じになってしまいましたね。
並べて比べてみましょう。
person = {height, weight | weigth / (height ** 2)};
bmi = {height, weight | weight / (height ** 2)};
この結果からもオブジェクトとクロージャが同じだということがわかると思います。
mieはこういうやわらかーい言語です。
やわらかいのが嫌だったら、自分でかくあるべき!という掟を
いろいろ作って固く仕上げれば良いのです。
でも最初からかたいものは、やわらかくすることができません。
オブジェクトとクロージャ、データと処理が同じだというのは、
なーんだ関数の引数と構造体のメンバを一緒にしただけじゃんかー。
そうです、そうですがそもそも関数と構造体が同じなのです。
だからこそフィールドとメソッドの区別がいらないわけです。
viva 統一性!
