Perl5.38 で追加されたclass構文を触ってみる
概要
Perl5.38で実験的機能としてclass構文が追加されたのでどんな感じで使えるのか試してみる
構文
まだ実験的機能なため、 use feature 'class';, use experimental 'class'; などで有効にしないと使えない
code:perl
use v5.38;
use experimental 'class';
class Point;
field $x = 0;
field $y = 0;
method init($dx = 0, $dy = 0) {
$x = $dx;
$y = $dy;
}
method print {
say "x: $x, y: $y";
}
my $p = Point->new;
$p->init(2, 4);
$p->print();
class 文でクラスとして利用できるパッケージを宣言する。使い方は package と同じ感じで, ブロックでスコープを作ったりバージョンを指定することもできる。 コンストラクタである new メソッドは自動的に生成され、自前で書くことはできない(実行時にエラーになる)
field 文でインスタンス変数を宣言する。スカラだけでなく配列、ハッシュの変数も宣言可能。なぜかリストで宣言しようとすると構文エラーになる。従来よく使われていた HashRef を bless したクラスと違って、宣言されたインスタンス変数はパッケージ外部から直接参照することができない。
method 文でメソッドを宣言する。 method 内では field で宣言されたインスタンス変数を参照することと、現在のオブジェクト自身を指す暗黙的に宣言された変数 $self を参照することができる。ほかはサブルーチンと同じような仕様でシグネチャの定義や匿名メソッドを作ることも可能。ちなみに $self からインスタンス変数を参照することもできない。(Java でいう this.x みたいな感じでインスタンス変数を参照することは無理)
class, field には attribute を設定することが可能でこれで上記の構文だけでは実現できない機能を実現している。
継承は class に :isa attribute を設定することでできる。
code:perl
use v5.38;
use experimental 'class';
class People {
field $name :param;
}
class User :isa(People) {
field $id :param;
}
従来のクラスと違って親クラスは1つしか指定することができない。class 構文で作られたクラスの @ISA は immutable な配列にされている。
親クラスのインスタンス変数は直接参照できない。
親クラスのモジュールロードはよしなにしてくれる
field に :param attribute を設定すると、その変数名の名前付き引数をコンストラクタに渡して初期化できるようになる。
field の初期値が定義されていない場合は名前付き引数が渡されていないとエラーになる
code:perl
class Point {
field $x :param;
field $y :param = 0;
}
my $p = Point~>new(); # error!
my $p = Point~>new(y => 1); # error!
my $p = Point~>new(x => -2); # ok
my $p = Point~>new(x => 3, y => 5); #ok 名前付き引数の名前をインスタンス変数名と別にしたい場合は:param attribute の引数に変えたい名前を指定する。
code:perl
class Point {
field $x :param(a);
field $y :param(b);
}
my $p = Point->new(a => 9, b => 4);
コンストラクタを呼ばれた直後にオブジェクトの中身をいじったり中身がどうなっているか知りたい場合は ADJUST ブロックを利用する。ブロック内では $self と fileds で宣言した変数が参照可能。
Moose の BUILD と同じようなもの。現状コンストラクタに渡ってきた引数を参照することもできないデバッグ用途以外で使うことはなさそう
code:perl
class Point {
field $x :param;
field $y :param;
ADJUST {
say "x => $x, y => $y";
}
}
my $p = Point->new(x => 1, y => 2); # x => 1, y => 2
その他
オブジェクトがどのクラス名は従来のクラスで作られたオブジェクト同様、Scalar::Util::blessed or bultin::blessed で調べることができる
class構文で作られたオブジェクトを Scalar::Util::reftype or builtin::reftype で調べると OBJECT という値が帰ってくる。従来のクラスで作られたオブジェクトの場合は bless したリファレンスの種類だった。
Data::Dumper でclass構文で作られたオブジェクトを調べようとすると cannot handle ref type 16 という警告が出てきてオブジェクトの中身は表示されない。
今後の予定
Moose などであったRole機能の実装
ADJUST ブロックでコンストラクタに渡された引数を受け取れるようにする
Accessor を自動生成する attribute の実装
メタプログラミングAPIの実装
コアモジュールをclass構文に対応させる
Attribute::Handlers で独自の field, class の attribute を定義できるようにとか?
従来のクラスと違う点
継承できるクラスは1つだけ
インスタンス変数はパッケージ外部から直接参照できない
inside-outオブジェクトと同じような感じ
bless しているリファレンスがないので、従来のオブジェクトとは異なる判定をされることがあるので注意