クラスとオブジェクト

この記事はPerl6 Advent Calendar 2011の6日目です。


前々回のuasiさんの記事が中々濃い内容だったので、補足としてPerl6のオブジェクトモデルについて簡単に解説しようと思います。

前提

Perl6の世界は(一部のnon-objectなプリミティブ型を除いて)オブジェクトで構成されています。全てのオブジェクトはクラスに属し、全てのクラスはルートクラスであるMuクラスを(そして一般的なクラスはMuを継承したAnyクラスを)継承しています。


オブジェクトに関する基本的な操作はMuクラスとAnyクラスに用意されていて、ユーザが独自に定義したクラスも暗黙的にAnyクラスを継承したものと見なされます。

クラスの定義

クラスを定義するにはclassキーワードを使います。

class C { ... }


クラスにはattributeとmethodを持たせる事が出来ます。

attribute

attributeの定義にはhasキーワードを使い、myを使って変数を宣言するのと同じように記述しますが、シジル(変数名の先頭に付く$,@,%)に続けてツイジルという記号を指定する事で、その属性へのアクセス制御を行います。


has $!attr のようにツイジルにエクスクラメーションを指定すると、その属性へのアクセサメソッドは自動的に生成されず、クラスの外からは読む事も書くこともできません。

has $.attr のようにツイジルにピリオドを指定すると、その属性への読み取りメソッドが自動的に生成され、$obj.attr のような記述で外部から値を読み込む事が出来ます。

has $.attr is rw のように、ツイジルにピリオドを指定した上で"is rw"トレイトを指定する事で、上記の読み取りメソッドに加えて書き込みメソッドが追加され、$obj.attr = 1 のように代入式の左辺値に置く事が出来るようになります。

class C {
    has $!internal;		#内部からのみアクセス可能
    has $.readonly; 		#外部からは読み取りのみ可能
    has $.writable is rw; 	#外部からも書き込み可能
}
method

methodの定義にはmethodキーワードを使い、subを使って関数を宣言するのと同じように記述します。オブジェクトに対するメソッド呼び出しはピリオドを使い、$obj.method() のように記述します。

class C {
    method do_it (Int $i) { ... }
}

コンストラク

Perl6では(一般的には)コンストラクタにnewメソッドを使います。クラス定義にnewメソッドを含めなかった場合は、親クラスから継承されたnewメソッドが利用できます(前述の通り、全てのクラスはMuクラスを継承しており、Muクラスには汎用のnewメソッドが用意されています)。


自分でnewメソッドを書く場合には、メソッドの最後にblessメソッドの返り値をそのまま返るようにします。一番単純な書き方ではblessに{属性名 => "値"}のハッシュを渡しますが、ハッシュの代わりにself.CREATEメソッドを返す事で、名前付き引数を使った初期化が可能になります(この説明は非常に乱暴かつ本質的でないのでS12-objects "Construction and Initialization"を参照してください)。

class C {
    has $!attr;

    method new ($i) { # 一番簡単?
        self.bless({ attr => $i })
    }      

    method new ($i) { # CREATEを使う版。やろうと思えばCREATEの引数に色々指定できる
    	self.bless(self.CREATE(), attr => $i);
    }

    method new ($i) { # 上と等価、スマートっぽい
    	self.bless(*, attr => $i); 
    }   
}

methodもmultiを指定する事でmultiple dispatch(過去記事参照)が可能なので、newメソッドを複数用意してあげるとイケてる感じのクラスが書けるかもしれませんね。

メタクラス

さて、Perl6ではオブジェクトの振る舞いを定義したり、オブジェクトの管理のためにメタクラスという概念を持っています。オブジェクトが保持するデータではなく、オブジェクトそのものの情報を得たり、オブジェクトの構造を改変するにはメタクラスにアクセスする必要があります。逆に言えば、メタクラスを利用することによって、あんなことやこんなことが出来るようになるわけですね。

あるオブジェクトに対して、それを管理しているメタクラスを得るにはHOWメソッドを使います。試しにmethodsメタメソッドを使ってオブジェクトに登録されているメソッドの一覧を取得してみましょう。

my $i = 1;
say $i.HOW.methods("i");

#> Int Num Rat abs Bridge chr succ pred sqrt (その他一杯、省略


ここでmethodsにオブジェクトの名前を渡しているのは、$iを管理しているメタクラスは他のオブジェクトも管理しているため、メソッドの一覧を取得する対象のオブジェクトを特定する必要があるためです。これは以下のように省略表記する事が出来ます。この場合、オブジェクト名は必要ありません。

my $i = 1;
say $i.^methods;


この他にも、オブジェクトに対してメソッドや属性を追加するadd_methodやadd_attribute、メソッドオブジェクトを取得するfind_method、継承関係を返すparentsなど、黒魔術だけでなくデバッグにも便利なメタメソッドがたくさん用意されています。これらのメタメソッドはsrc/perl6/metamodel以下に定義されていますので、気が向いたら覗いて見ると良いでしょう。


今挙げたうちの一つadd_methodについてですが、実はオブジェクトを生成する際、メソッドの追加にはadd_methodが内部的に呼ばれています。前回のuasiさんの記事では「メタクラスを差し替えてadd_methodの挙動を独自のものに置き換える事で、追加するメソッド名に手を加えている」というワザが使われているのでした。