このパートでは、オブジェクト指向の中心的機構であるクラスとメソッドについて取り扱います。
レッスン1 クラス
fluorite-7では、「クラス名: {
};
」でクラスを生成し、宣言します。
$ fl7 'Human: {}; Human'
{}
クラスをインスタンス化するには、クラスに続けて波括弧{
}
を記述します。
$ fl7 'Human: {}; Human{}'
{}
レッスン2 メソッド
クラスメソッドを定義するには、クラスを定義する際に、波括弧の中で「メソッド名: _,
引数列->
式」と記述します。
クラスメソッドを利用するには、「インスタンス::
メソッド名(
引数列)
」と記述します。
$ fl7 'Human: {attack: _, target -> "$(target)を殴りつけた。";}; Human{}::attack("スライム")'
スライムを殴りつけた。
Human: {
attack: _, target -> "$(target)を殴りつけた。";
};
Human{}::attack("スライム")
レッスン3 コンストラクタ
クラスの定義中に「new: _ -> _{};
」と記述すると、「クラス名::new()
」という形でコンストラクタを呼び出せます。
$ fl7 'Human: {new: _ -> _{}; attack: _, target -> "$(target)を殴りつけた。";}; Human::new()::attack("スライム")'
スライムを殴りつけた。
Human: {
new: _ -> _{};
attack: _, target -> "$(target)を殴りつけた。";
};
Human::new()::attack("スライム")
レッスン4 フィールド
インスタンス化時にフィールドを定義するには、コンストラクタ内の_{
と}
の間で、「フィールド名=
値」を;
で区切って並べればよいです。
;
は無駄に多く書いても、単に無視されます。
フィールドにアクセスするには、「インスタンス.
フィールド名」と記述します。
$ fl7 'Human: {new: _ -> _{name = "大石";}; attack: _, target -> "$(target)を殴りつけた。";}; Human::new().name'
大石
Human: {
new: _ -> _{
name = "大石";
};
attack: _, target -> "$(target)を殴りつけた。";
};
Human::new().name
レッスン5 this
メソッド内では、変数_
により呼び出しコンテキストに紐づいたインスタンス、所謂thisを得ることができます。
$ fl7 'Human: {new: _ -> _{name = "大石";}; attack: _, target -> "$(_.name)は$(target)を殴りつけた。";}; Human::new()::attack("スライム")'
大石はスライムを殴りつけた。
Human: {
new: _ -> _{
name = "大石";
};
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
};
Human::new()::attack("スライム")
レッスン6 コンストラクタの引数
コンストラクタnew: _ -> _{};
は、_
と->
の間に「,
引数名」を並べることで、引数を取ることができます。
$ fl7 'Human: {new: _, name -> _{name = name;}; attack: _, target -> "$(_.name)は$(target)を殴りつけた。";}; Human::new("大石")::attack("スライム")'
大石はスライムを殴りつけた。
Human: {
new: _, name -> _{
name = name;
};
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
};
Human::new("大石")::attack("スライム")
レッスン7 クラスの継承
「子クラス名:
親クラス名{
};
」で、親クラスを継承したクラスを作り、宣言することができます。
子クラスのコンストラクタでは、_{}
の代わりに「^.new(_)
」または「^.new(_;
引数列)
」のようにして親クラスのコンストラクタを呼んでください。
$ fl7 'Human: {new: _, name -> _{name = name;}; attack: _, target -> "$(_.name)は$(target)を殴りつけた。";}; Magician: Human{new: _, name -> ^.new(_; name);}; Human::new("大石")::attack("スライム")'
大石はスライムを殴りつけた。
Human: {
new: _, name -> _{
name = name;
};
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
};
Magician: Human{
new: _, name -> ^.new(_; name);
};
Magician::new("大石")::attack("スライム")
レッスン8 メソッドのオーバーライド
子クラスで同名のメソッドを定義すると、メソッドをオーバーライドできます。
$ fl7 'Human: {new: _, name -> _{name = name;}; attack: _, target -> "$(_.name)は$(target)を殴りつけた。";}; Magician: Human{new: _, name -> ^.new(_; name); attack: _, target -> "$(_.name)は$(target)に魔法をかけた。";}; Human::new("大石")::attack("スライム"), Magician::new("大石")::attack("スライム"),'
大石はスライムを殴りつけた。
大石はスライムに魔法をかけた。
Human: {
new: _, name -> _{
name = name;
};
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
};
Magician: Human{
new: _, name -> ^.new(_; name);
attack: _, target -> "$(_.name)は$(target)に魔法をかけた。";
};
Human::new("大石")::attack("スライム"),
Magician::new("大石")::attack("スライム"),
レッスン9 親クラスのメソッド呼び出し
「^.
メソッド名(_)
」または「^.
メソッド名(_;
引数列)
」で親クラスのメソッドを呼び出すことができます。
$ fl7 'Human: {new: _, name -> _{name = name;}; attack: _, target -> "$(_.name)は$(target)を殴りつけた。";}; Magician: Human{new: _, name -> ^.new(_; name); attack: _, target -> ^.attack(_; target) & "さらに魔法もかけた。";}; Human::new("大石")::attack("スライム"), Magician::new("大石")::attack("スライム"),'
大石はスライムを殴りつけた。
大石はスライムを殴りつけた。さらに魔法もかけた。
Human: {
new: _, name -> _{
name = name;
};
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
};
Magician: Human{
new: _, name -> ^.new(_; name);
attack: _, target -> ^.attack(_; target) & "さらに魔法もかけた。";
};
Human::new("大石")::attack("スライム"),
Magician::new("大石")::attack("スライム"),
レッスン10 種明かし
fluorite-7には言語仕様としてはクラスもメソッドも存在しません。
今回新たに登場した文法は以下のものだけです。
- 「オブジェクト
::
メンバ名」で、メンバ関数の第1引数にオブジェクトを部分適用した関数を生成する。 - オブジェクト初期化子の中で
^
と書くと親オブジェクトが得られる。
::
演算子をデリゲート演算子、デリゲート演算子を適用することをデリゲート生成と呼びます。
^
は親オブジェクト参照子と呼びます。
fluorite-7のオブジェクト指向は、継承付きオブジェクトとデリゲートという単純な機構を組み合わせて出来ています。
また、^
に似た機能のオブジェクト参照子$
もあります。
- オブジェクト初期化子の中で
$
と書くとそのオブジェクトが得られる。
$ fl7 'obj: {a: 1}{a: 2; p: () -> ^; o: () -> $}; obj.p().a, obj.o().a,'
1
2
obj: {
a: 1
}{
a: 2;
p: () -> ^;
o: () -> $;
};
obj.p().a,
obj.o().a,
レッスン11 サンプルコードの解釈
それでは、以上のことを踏まえたうえで最後に登場したHuman
クラスの登場するサンプルコードを見てみましょう。
Human: {
new: _, name -> _{
name = name;
};
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
};
Magician: Human{
new: _, name -> ^.new(_; name);
attack: _, target -> ^.attack(_; target) & "さらに魔法もかけた。";
};
Human::new("大石")::attack("スライム"),
Magician::new("大石")::attack("スライム"),
クラスの定義
まずHuman: {
~};
という記述は、新しいオブジェクトで初期化されたHuman
という変数を宣言しただけにほかなりません。
Magician: Human{
~};
という記述も、変数Human
に格納されたオブジェクトを継承した新しいオブジェクトを作り、Magician
と名付けただけにほかなりません。
オブジェクトが継承する際には親オブジェクトのプロパティがメンバとして見えるようになるため、クラスの継承と同じ機能が実現されます。
fluorite-7のクラスは継承付きオブジェクトで実現されます。
メソッド
{
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
}
この部分では、オブジェクトに対してattack
という名前のキーで_, target -> "$(_.name)は$(target)を殴りつけた。"
という値を持つプロパティを宣言しています。
この値は_
とtarget
を引数に取って"$(_.name)は$(target)を殴りつけた。"
を返す関数です。
fluorite-7のメソッドはクラスに格納された関数です。
根底クラスのコンストラクタ
new: _, name -> _{
name = name;
};
コンストラクタも関数が格納されたプロパティです。
コンストラクタの本文には、その関数の引数を使用した式である_{name = name;}
と書かれています。
実はコンストラクタ名はnew
以外でも何でもよいです。
根底クラスのコンストラクタの呼び出し
Human: {
new: _, name -> _{
name = name;
};
};
Human::new("大石")
オブジェクトHuman
に対して、::new
を付けるとどうなるでしょうか。
デリゲート演算子::
の説明は次のものでした。
「オブジェクト
::
メンバ名」で、メンバ関数の第1引数にオブジェクトを部分適用した関数を生成する。
メンバnew
が表す関数は_, name -> _{name = name;}
です。
これの第1引数にHuman
を部分適用すると、name -> Human{name = name;}
となります。
これはクラスHuman
の動作を継承したインスタンスを生成していることになります。
また、インスタンスが持つフィールドとして、引数name
をname
に代入しています。
コンストラクタの第1引数_
には、クラスがやってくるということになります。
Human::new
はname -> Human{name = name;}
と等価でした。
この関数("大石")
を適用すると、Human{name = "大石";}
が得られます。
これはname
というプロパティつきの、クラスHuman
を親として持つオブジェクトです。
コンストラクタの第2引数以降には、::new()
する際の引数がやってくることになります。
親クラスのコンストラクタの呼び出し
子クラスMagician
のコンストラクタにおける親クラスのコンストラクタ呼び出しは次のようでした。
Human: {
new: _, name -> _{
name = name;
};
};
Magician: Human{
new: _, name -> ^.new(_; name); // ←ここ
};
Human::new("大石")
まず、_
には「実際のクラス」がやってきます。
それは、このコンストラクタが定義されているクラスかもしれませんし、その更に子クラスかもしれません。
実際のクラスが分からないため、引数で受け取ります。
そして、根底クラスでは実際のクラスである_
を親としてインスタンスオブジェクトを生成しています。
Magician
クラスの初期化子の中に書かれている^
で得られるのは、親オブジェクトであるHuman
クラスです。
^.new
はHuman
クラスのメンバ関数new
です。
さっきまではnew
はデリゲート演算子::
で呼び出していましたが、ここではメンバアクセサ.
で呼び出しています。
それは、Human::new
はクラスHuman
のインスタンスを生成してしまうからです。
new
の第1引数には実際のクラスを指定しなければなりません。
それには、new
そのものを参照し、実際のクラスである_
を明示的に与えればよいです。
それに残りの引数であるname
を与えたものが^.new(_; name)
です。
結果、Magician::new("大石")
という呼び出しではMagician
クラスを親オブジェクトとしたインスタンスオブジェクトが得られます。
メソッド呼び出し
Human: {
new: _, name -> _{
name = name;
};
attack: _, target -> "$(_.name)は$(target)を殴りつけた。";
};
Magician: Human{
new: _, name -> ^.new(_; name);
attack: _, target -> ^.attack(_; target) & "さらに魔法もかけた。";
};
Magician::new("大石")::attack("スライム"),
このコードでは、Magician
クラスのインスタンスであるMagician::new("大石")
のメソッドattack
を、"スライム"
という引数で呼び出しています。
Magician::new("大石")::attack
の時点で何が起こるのかというと、Magician
クラスのインスタンスのメンバattack
を参照し、第1引数にそのインスタンスを部分適用しています。
Magician
クラスのインスタンスは、Magician
クラスを継承しているので、Magician
クラスのattack
メソッドがメンバattack
として見えています。
もしMagician
クラスにattack
メソッドがなかった場合、Human
クラスのattack
メソッドが見えます。
Magician
クラスのattack
メソッドとは、_, target -> ^.attack(_; target) & "さらに魔法もかけた。"
のことです。
これは、インスタンスである_
ともう一つの引数target
を受け取ると、Magician
クラスの親オブジェクトであるHuman
クラスのメンバattack
を、インスタンス_
と引数target
で起動し、その結果を文字列"さらに魔法もかけた。"
と結合して返します。
Human
クラスが持つattack
メソッド関数_, target -> "$(_.name)は$(target)を殴りつけた。"
は、_
にインスタンスが、target
には"スライム"
が指定されて呼び出されます。
_.name
はインスタンスのメンバname
ですが、これはHuman
クラスのコンストラクタnew: _, name -> _{name = name;};
で代入したプロパティname
がフィールドとして見えています。
結果、Human
クラスのattack
メソッドは"大石はスライムを殴りつけた。"
を返します。
Magician
クラスのattack
メソッド^.attack(_; target) & "さらに魔法もかけた。"
の前半部分^.attack(_; target)
は"大石はスライムを殴りつけた。"
となるので、後半の"さらに魔法もかけた。"
と文字列結合して"大石はスライムを殴りつけた。さらに魔法もかけた。"
が返ります。
最終的に、Magician::new("大石")::attack("スライム")
は"大石はスライムを殴りつけた。さらに魔法もかけた。"
となります。
まとめ
長くなりましたが、fluorite-7にはオブジェクト指向を成立させる機構が存在ます。
- デリゲート演算子
object::method
は、object.method
の第1引数にobject
を部分適用した関数を得る。 - オブジェクト初期化子の中で
$
と書くと、そのオブジェクトを得る。 - オブジェクト初期化子の中で
^
と書くと、親オブジェクトを得る。