mieの最近のブログ記事
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 統一性!
mieで1+1を計算するプログラムは次のようになります。
1.+ <+[1]!;
--> 2
きんもーっ☆
…いや、まぁ普通にも書けます。
1 + 1;
--> 2
でなんで 1.+ <+[1]! が 1 + 1 となるのかというところを解説します。
mieでオブジェクトを定義するには次のようにします。
pt = [x = 10, y = 25];
これでx,y成分を持つ座標オブジェクトptが定義されました。
「名前 = 値」と書いて名前に値を束縛します。
上の式は、xという名前に整数10、yという名前に整数25を束縛したオブジェクトを
ptという名前に束縛する、という意味になります。
xやyに当たるオブジェクトの中身のことを要素と呼びます。
オブジェクトの要素を参照することを抽出と呼びます。
抽出を行なうにはオブジェクトの後にシンボルを記述します。
シンボルのリテラルは「.」で始まります。
よってptオブジェクトの要素xを抽出するには次のように書きます。
pt .x;
--> 10
空白文字は省略できるので、C言語などのメンバ参照と同じ見た目になります。
pt.y;
--> 25
mieではSmalltalkやRuby同様に整数はオブジェクトです。
加減乗除などのメソッドは整数オブジェクトの要素として定義されています。
先程のpt.xの例と同じように、1オブジェクトの要素+メソッドを抽出することができます。
1.+;
--> <builtin:Mie::IntegerObj::Add>
次にオブジェクトの拡張生成について。
ptオブジェクトを2次元から3次元に拡張したい場合、
次のように新たにz成分を追加したオブジェクトを生成できます。
pt <+ [z = 0];
--> [x = 10, y = 25, z = 0]
「<+」はメタ結合と呼び、2つのオブジェクトを重ね合わせたオブジェクトを生成します。
引数を2乗する関数squareを定義してみます。
square = {x|x * x};
引数xを受け取り、xにxを掛けた値を返す、ということです。
ここで{}は基本的には[]と同じで、オブジェクトリテラルです。
主に関数的に利用する場合に使います。
mieでは関数とオブジェクトは全く同等なデータ構造です。
定義されたsquareに対して整数3を要素に持つオブジェクトをメタ結合します。
すると引数(要素)xに3が束縛された状態になります。
square <+ [3];
--> {x = 3| x * x}
この時点ではまだ束縛されただけでx*xは計算されていません。
x*xを計算させるためには評価をする必要があります。
評価は対象となる式の後に「!」と記述します。
3を引数に束縛して、評価すると9が返ってきます。
square <+ [3]!;
--> 9
適用(メタ結合)してから評価する、という2段階で関数実行が行われることになります。
適用+評価はより簡潔に書けます。これはシンタックスシュガーです。
単に空白文字で区切ってオブジェクトを並べると、メタ結合した後に評価が行われます。
square [3];
--> 9
お好みで空白文字を取り除くことも可能です。
square[3];
--> 9
適用と評価が分離しているため、適用後のオブジェクトを一旦変数に保持しておき、
後で評価を行うということも可能です。明示的な遅延評価ですね。
これが役に立つのかどうかはわかりません。
tmp = square <+ [3];
tmp!;
--> 9
さらにもう一つのシンタックスシュガーを紹介します。
名前が演算子文字で始まる要素の場合、
object <element> arg;
と書くと以下のように解釈されます。
object.<element> [arg]
つまり、
1 + 1;
は次のようになります。
1.+ [1]
さらにこれに対して適用+評価のシンタックスシュガーを展開すると
1.+ <+[1]!
となるわけです。
これで、オブジェクト1の要素+メソッドに、要素にオブジェクト1を持つオブジェクトをメタ結合して
評価すると、オブジェクト2が得られました。ふぅー。
ちなみに、squareの例ですが関数リテラルで直接書くと次のようになります。
{x|x * x}[3];
--> 9
これもシンタックシュガーを使わずに書くと次のようになります。
{x|x.* <+[x]!}<+[3]!;
--> 9
なんとなく書けるうちに書いておいて少しでも知ってもらえたらなと。
需要があるようなら定期的に書いていきたいと思います。
わからないところとか気になるところとかコメント頂けると幸いです。
