Perl6 (Raku) におけるオブジェクト指向のまとめ
目次
クラスの定義
-
class
というキーワードでクラスを宣言 -
クラス名.new()
でインスタンスの生成
class Foo {}
my $foo = Foo.new;
フィールド、メソッド
- フィールドは
has $.フィールド名
で宣言します- 定義するときは
$.フィールド名
とし、メソッドなどからアクセスするときは$!フィールド名
の方が良いです。 理由としては.
トゥイジルは、アクセッサの定義と!
トゥイジルによるアクセッサの定義を行うため、処理が重いからです。
- 定義するときは
- メソッドは
method
というキーワードで宣言します
class Point {
has $.x;
has $.y;
method add($other) {
Point.new(x => $!x + $other.x, y => $!y + $other.y);
}
}
my $p1 = Point.new(x => 1, y => 2);
my $p2 = Point.new(x => 3, y => 4);
say $p1.add($p2);
# => Point.new(x => 4, y => 6)
- フィールドに関して言えば、my による変数定義と同様に、型を指定することもできます
class Point {
has Numberic $.x;
has Numberic $.y;
# ...
}
コンストラクタ
- new というメソッドがコンストラクタになります
- デフォルトでは、
has
で宣言したパブリックフィールドを名前付き引数とするコンストラクタが定義されています
class Point {
has $.x;
has $.y;
method new(:$x, :$y) {
self.bless(:$x, :$y);
}
}
say Point.new(x => 10, y => 3.14); # => Point.new(x => 10, y => 3.14)
say Point.new(); # => Point.new(x => Any, y => Any)
- 必ずフィールドを初期化する必要があるとき
- new でコンストラクタを作らない場合は、フィールドに
is required
を付けます - new で名前付き引数を取るコンストラクタを作る場合は、引数の末尾に
!
を付けます - new で固定引数を取るコンストラクタを作ります
- new でコンストラクタを作らない場合は、フィールドに
class Point {
has $.x is required;
has $.y is required;
}
say Point.new(x => 10, y => 3.14); # => Point.new(x => 10, y => 3.14)
say Point.new(); # => Error!
class Point {
has $.x;
has $.y;
method new(:$x!, :$y!) {
self.bless(:$x, :$y);
}
}
say Point.new(x => 10, y => 3.14); # => Point.new(x => 10, y => 3.14)
say Point.new(); # => Error!
class Point {
has $.x;
has $.y;
method new($x, $y) {
self.bless(:$x, :$y);
}
}
say Point.new(10, 3.14); # => Point.new(x => 10, y => 3.14)
say Point.new(); # => Error!
クラスメソッド、クラス変数
- クラス変数は
my
キーワードを使います - クラスメソッドは オブジェクトのフィールドにアクセスしていないメソッドは、クラスメソッドとして呼ぶことができます
- 任意のメソッドがクラスメソッドとして呼ばれた場合は、
self.defined
が false になります - 任意のメソッドがインスタンスメソッドとして呼ばれた場合は、
self.defined
が true になります
- 任意のメソッドがクラスメソッドとして呼ばれた場合は、
class User {
has $.name;
my $.counter = 0;
method new($name) {
User.counter++;
self.bless(:$name);
}
method get_count() {
User.counter;
}
}
my $a = User.new('a');
my $b = User.new('b');
say User.counter; # => 2
say User.get_count; # => 2
アクセス権
- public
has $.フィールド名
method メソッド名
- 制限付き
has $.フィールド名 is readonly
has $.フィールド名 is rw
- private
has $!フィールド名
method !メソッド名
- protected
submethod メソッド名
オーバーロード
同じ名前のメソッドを multi
で定義することでオーバーロードすることができます。
class Sample {
multi method foo(Int $a where { $a >= 0 }) {
say "take $a apples";
}
multi method foo(Int $a) {
say "give " ~ (-$a) ~ " apples";
}
multi method foo(Str $a) {
say "Hello $a!"
}
}
Sample.foo(3); # => take 3 apples
Sample.foo(-2); # => give 2 apples
Sample.foo("Alice"); # => Hello Alice!
継承、オーバーライド
- 継承をするには
class 子クラス is 親クラス
と書きます -
class 子クラス is 親クラス1 is 親クラス2
で多重継承になります - 親クラスのメソッドへの参照は
nextsame
を使います
class A {
method foo {
say "A";
}
}
class B is A {
method foo {
say "B";
nextsame;
}
}
B.foo;
# => B
# => A
抽象クラス、インターフェース
ロール(Roles)は、Javaでいうところの抽象クラスやインターフェースに当たるものです。 クラスは多重継承したときに同じ名前のメソッドが存在するときは C3 という メソッド解決順序(Method Resolution Order)アルゴリズムを使ってどちらの関数を呼ぶか決定しますが、 ロールを多重継承したときに同じ名前のメソッドが存在するときはエラーになります。 そのときは、子クラスの方でそのメソッドを実装(オーバーライド)しなければなりません。
- ロールは
role
キーワードで宣言します - ロールを実装したいクラスは
does
キーワードを使って宣言します
role A {
method foo {
say "A";
}
}
role B {
method foo {
say "B";
}
}
class C does A does B {
# この場合はfooメソッドが競合してしまうので、オーバーライドしないとエラーになる
method foo {
say "A";
say "B";
}
}
C.foo;
# => A
# => B
演算子オーバーライド
演算子には prefix, postfix, infix, circumfix, postcircumfix の5種類があります。
種類 | 定義 | 例 |
---|---|---|
prefix | prefix:<!> | !3 |
postfix | postfix:<!> | 3! |
infix | infix:<+> | 1 + 2 |
circumfix | circumfix:<[ ]> | [1] |
postcircumfix | postcircumfix:<[ ]> | a[3] |
class Point {
has $.x;
has $.y;
method add($other) {
Point.new(x => $!x + $other.x, y => $!y + $other.y);
}
}
# prefix: -
# Retruns negated vector
# ex) -a
multi sub prefix:<->(Point $p) {
Point.new(x => -$p.x, y => -$p.y);
}
say -Point.new(x => 1, y => 2);
# => Point.new(x => -1, y => -2)
# postfix: ~
# Retruns transposed vector
# ex) a~
multi sub postfix:<~>(Point $p) {
Point.new(x => $p.y, y => $p.x);
}
say Point.new(x => 1, y => 2)~;
# => Point.new(x => 2, y => 1)
# infix: +
# Add two vectors
# ex) a + b
multi sub infix:<+>(Point $lhs, Point $rhs) {
$lhs.add($rhs);
}
say Point.new(x => 1, y => 2) + Point.new(x => 3, y => 4);
# => Point.new(x => 4, y => 6)
# circumfix: []
# Computes distance from origin
# ex) [a]
multi sub circumfix:<[ ]>(Point $p) {
sqrt($p.x ** 2 + $p.y ** 2);
}
say [Point.new(x => 3, y => 4)];
# => 5
# postcircumfix: []
# Add two vectors
# ex) point[1, 2]
multi sub postcircumfix:<[ ]>(Point $p, @index where { @index.elems == 2 }) {
Point.new(x => $p.x + @index[0], y => $p.y + @index[1]);
}
say Point.new(x => 1, y => 2)[3, 4];
# => Point.new(x => 4, y => 6)
演算子の定義
演算子は自分で定義することもできます。
次の例は、n日後の日にちを取得するための演算子 days
の定義方法です。
sub infix:<days>(Int $day, %args) {
if %args<ago>:exists {
return Date.new(DateTime.now).later(days => $day);
}
}
say 3 days :ago;