0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

fluorite-7 日本語チュートリアル Part 10 クラスとメソッド

Last updated at Posted at 2020-06-22

チュートリアルトップ

←前回 次回→

このパートでは、オブジェクト指向の中心的機構であるクラスとメソッドについて取り扱います。

レッスン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の動作を継承したインスタンスを生成していることになります。
また、インスタンスが持つフィールドとして、引数namenameに代入しています。

コンストラクタの第1引数_には、クラスがやってくるということになります。


Human::newname -> 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クラスです。
^.newHumanクラスのメンバ関数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を部分適用した関数を得る。
  • オブジェクト初期化子の中で$と書くと、そのオブジェクトを得る。
  • オブジェクト初期化子の中で^と書くと、親オブジェクトを得る。

Part 11

次回→

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?