M.Hiroi's Home Page

Linux Programming

お気楽 Perl プログラミング超入門

[ PrevPage | Perl | NextPage ]

オブジェクト指向 (後編)

今回はオブジェクト指向の目玉機能である「継承」を説明します。Perl と他のオブジェクト指向言語を比べると、継承の基本的な考え方は同じですが、そこは Perl のことですから、やっぱり個性的なのです。

●継承

オブジェクト指向 (前編) で簡単に説明したように、継承はクラスに「親子関係」を持たせる機能です。子供のクラスは親のクラスの性質を受け継ぐことができます。あるクラスを継承する場合、その元になるクラスを「スーパークラス」と呼び、継承したクラスを「サブクラス」と呼びます。この呼び方はプログラミング言語によってまちまちで統一されていません。C++の場合は、元になるクラスを「基本クラス」といい、継承するクラスを「派生クラス」とか「導出クラス」といいます。

継承には「単一継承」と「多重継承」の二種類があります。単一継承は、ただひとつのクラスからしか機能を継承することができません。したがって、クラスの階層は次のような木構造で表すことができます。


       図 : 単一継承におけるクラスの階層

継承は何段階に渡って行われてもかまいません。たとえばクラス E の場合、スーパークラスが B で、B のスーパークラスが A に設定されています。サブクラスは複数あってもかまいません。たとえば、A のサブクラスは B, C, D の 3 つあり、B のサブクラスは E, F の 2 つあります。上図では、クラス A のスーパークラスはありませんが、ほかのクラスではただひとつのスーパークラスを持っています。プログラミング言語では、Java, Smalltalk, Ruby などが単一継承です。

これに対し多重継承は、複数のクラスから機能を継承することができます。このため、クラスの階層は木構造ではなく、次のようなグラフで表すことができます。


  図 : 多重継承におけるクラスの階層

クラス E に注目してください。スーパークラスには B と C の 2 つがあります。多重継承では、単一継承と同じくサブクラスを複数持つことができ、なおかつ、スーパークラスも複数持つことができるのです。C++, Common Lisp Object System (CLOS), Python は多重継承をサポートしています。そして肝心の Perl は何と多重継承なのです。

実を言うと、M.Hiroi は多重継承に対してあまりいいイメージを持っていません。私見ですが、多重継承はメリットよりもプログラムを複雑にするデメリットの方が大きいのではないか、と思っています。とくに、上図のクラス A, B, C, E のような菱形の関係をC++でプログラムする場合、とても複雑な問題を引き起こすことが知られています。

ところが Perl の場合、継承であれば当然持っているはずの機能が削除されていて、そのことにより多重継承の複雑さはC++よりも軽減されているように思います。そこで、まず一般的な継承の仕組みから説明しましょう。

●継承の仕組み

一般のオブジェクト指向言語の場合、継承によって引き継がれる性質は定義されたデータやメソッドになります。データを受け継ぐことを「属性の継承」、メソッドを受け継ぐことを「実装の継承」と区別して呼ぶことがあります。次の図を見てください。


    図 : 一般のオブジェクト指向言語における継承

クラス Foo には変数 a, b [*1] とアクセスメソッド get_a, get_b が定義されています。次に、クラス Bar を定義します。Bar は Foo を継承し、Bar 固有の変数 c とアクセスメソッド get_c が定義されています。Foo と Bar のインスタンスを生成すると、上図に示したように、Bar のインスタンスにはクラス Foo で定義された変数 a, b も含まれます。これが属性の継承です。

Foo のインスタンスを生成すると、もちろん変数 a, b は含まれていますが、Bar のインスタンスとメモリを共有することはありません。クラスはオブジェクトの設計図です。設計に共通な部分があったとしても、それから生み出されるインスタンスは別々の実体で、メモリを共有することはないのです。

クラス Bar にはメソッド get_c しか定義されていませんが、クラス Foo を継承したことにより、メソッド get_a と get_b を利用することができます。これが実装の継承です。Bar のインスタンスに対して get_a を呼び出すと、クラス Bar には get_a が定義されていないので、スーパークラス Foo を調べ、そこで定義されている get_a が呼び出されます。もちろん、取り出される値は、Bar のインスタンスにある変数 a の値です。

一般のオブジェクト指向言語では、このように属性と実装が継承されるのですが、Perl はそうではありません。実装は継承されますが、属性は継承されません。つまり、Perl では継承されるのはメソッドだけなのです。いやはやなんとも、Perl のオブジェクト指向は M.Hiroi が常識だと思っていたことを次々と破壊してくれます。このようなオブジェクト指向もあるのだなあ、とたいへん驚きました。

このように Perl では属性の継承が行われないため、スーパークラスとサブクラスで共通に使用するデータ構造は、プログラマが決定する必要があります。といっても難しい話ではなく、ハッシュを使えばうまくいきます。

-- note --------
[*1] インスタンス内に保持される変数を「インスタンス変数」と呼ぶことがあります。

●単一継承

それでは具体的に Perl の継承を説明していきましょう。Perl の継承は、パッケージに用意されている特別な配列 @ISA で定義します。@ISA にセットしたクラス名 (パッケージ名) がスーパークラスとなります。この @ISA に複数のクラス名を定義すると、それら複数のクラスを継承する多重継承となります。継承に必要な設定はこれだけです。

では実際に、上図のクラスをプログラムしてみましょう。まずクラス Foo を定義します。

リスト : クラス Foo の定義

use strict;
use warnings;

package Foo;

sub new {
    my ($type, $a, $b) = @_;
    my $obj = {a => $a, b => $b};
    bless $obj, $type;
    $obj;
}

# アクセスメソッド
sub get_a {
  my $obj = shift;
  $obj->{'a'};
}

sub get_b {
  my $obj = shift;
  return $obj->{'b'};
}

インスタンスを生成するメソッドを「コンストラクタ」と呼びます。元々はC++の用語でが、Perl でも使われることがあります。コンストラクタを継承できるように、bless には引数で与えられたクラス名 $type を渡します。オブジェクト指向 (前編) では、クラス名を省略するとメソッドの継承が動作しないと書きましたが、それだけではなく、クラス名を直接書いてもコンストラクタの継承は動作しないのです。

たとえば、Foo を継承するクラス Foo1 を考えましょう。Foo1 には新しいインスタンス変数を定義せずに、メソッドだけ追加することにします。この場合、Foo のコンストラクタをそのまま継承できるはずです。つまり、Foo1->new() は Foo の new() を呼び出しますが、それでも Foo1 のインスタンスを生成しなくてはいけません。

ところが、bless で Foo を直接指定すると、Foo1->new() と呼び出しても作成するインスタンスは Foo のインスタンスになってしまいます。このため、bless には直接クラス名を記述するのではなく、引数で与えられたクラス名を渡すようにしてください。

メソッドの定義は簡単ですね。与えられたインスタンスから値を取り出すだけです。次にクラス Bar を定義します。

リスト : クラス Bar の定義

package Bar;
our @ISA = ('Foo');        # Foo を継承

# インスタンスの生成
sub new {
    my ($type, $a, $b, $c) = @_;
    my $obj = {a => $a, b => $b, c => $c};
    bless $obj, $type;
    $obj;
}

# アクセスメソッド
sub get_c {
    my $obj = shift;
    return $obj->{'c'};
}

まず @ISA にスーパークラス Foo をセットします。クラス Bar は新しいインスタンス変数 c を使うので、自分のコンストラクタ new() を定義します。このように、継承したクラスのメソッドとは違う働きをさせる場合、同名のメソッドを定義することで行うことができます。これを「オーバーライド (override)」といいます。

メソッドを選択する仕組みから見た場合、オーバーライドは必然の動作です。メソッドはサブクラスからスーパークラスに向かって検索しますので、スーパークラスのメソッドよリサブクラスのメソッドが先に選択されるのは当然のことなのです。

●擬似クラス SUPER

メソッドをオーバーライドした場合、サブクラスのメソッドからスーパークラスのメソッドを呼び出すことができると便利です。new では無名のハッシュにキー a, b, c をセットしていますが、スーパークラスのコンストラクタを呼び出してインスタンスを生成し、そこにキー c を追加することもできます。この場合は、次のようなプログラムになります。

リスト : インスタンスの生成 (2) 

sub new {
    my ($type, $a, $b, $c) = @_;
    my $obj = $type->Foo::new($a, $b);
    $obj->{'c'} = $c;
    $obj;
}

このように、オブジェクトにハッシュを使うことで、インスタンス変数の追加を簡単に行うことができます。Perl の場合、オブジェクトに配列を使うこともできますが、配列は末尾方向にしか拡張することができないため、どのサブクラスが何番目の要素を使うかで問題が発生します。ハッシュを使えば、同じ名前を使わないように注意するだけですみます。

ところが、これではクラス名 Foo を直接書いているため、継承の階層を変更したとき、たとえば Foo と Bar のあいだに新しいクラスを挿入する場合、プログラムを修正しなくてはなりません。これを避けるため、Perl には SUPER という疑似クラスが用意されています。

リスト : インスタンスの生成 (3) 

sub new {
    my ($type, $a, $b, $c) = @_;
    my $obj = $type->SUPER::new($a, $b);
    $obj->{'c'} = $c;
    $obj;
}

これで @ISA の中からクラスを検索し、適切なコンストラクタが呼び出されます。Foo と Bar のあいだに新しいクラスを挿入するにしても、配列 @ISA を変更するだけで済むのです。

それでは実行してみましょう。

リスト : sample1401.pl

use strict;
use warnings;

package Foo;

・・・省略・・・

package Bar;

・・・省略・・・

package main;

my $o1 = Foo->new(1, 2);
my $o2 = Bar->new(10, 20, 30);

print $o1->get_a(), "\n";    #  1 を出力
print $o2->get_a(), "\n";    # 10 を出力
print $o2->get_c(), "\n";    # 30 を出力
$ perl sample1401.pl
1
10
30

メソッド get_a は Bar に定義されていませんが、スーパークラス Foo のメソッド get_a が呼び出されて、変数 a の値を求めることができます。また、Bar のインスタンスに対して get_c を呼び出せば、変数 c の値を求めることができます。

●制限付き連結リスト

それでは簡単な例題として、以前作成した 連結リスト LinkList を継承して、格納する要素数を制限する連結リスト FixedList というクラスを作ってみましょう。次のリストを見てください。

リスト : 制限付き連結リスト (FixedList.pm)

use strict;
use warnings;
use LinkList;

package FixedList;
our @ISA = ('LinkList');

# インスタンスの生成
sub new {
    my ($type, $limit) = @_;
    my $obj = $type->SUPER::new();
    $obj->{'limit'} = $limit;
    $obj->{'size'} = 0;
    $obj;
}

# 満杯か?
sub is_full {
    my $xs = shift;
    $xs->{'size'} == $xs->{'limit'};
}

# データの挿入
sub insert_nth {
    my ($xs, $n, $x) = @_;
    return $Cell::nil if $xs->is_full();
    my $r = $xs->SUPER::insert_nth($n, $x);
    $xs->{'size'}++ if !Cell::null($r);
    $r;
}

# データの削除
sub delete_nth {
    my ($xs, $n) = @_;
    my $r = $xs->SUPER::delete_nth($n);
    $xs->{'size'}-- if !Cell::null($r);
    $r;
}

1;

FixedList は指定した上限値までしか要素を格納できません。LinkList で要素を追加するメソッドは insert_nth で、削除するメソッドは delete_nth です。この 2 つのメソッドをオーバーライドすることで、FixedList の機能を実現することができます。

FixedList は LinkList を継承するので、@ISA に LinkList を指定します。このほかにも、use base でスーパークラスを指定する方法があります。

リスト : use base の使用例

use strict;
use warnings;

package FixedList;
use base ('LinkList');

use base は指定したパッケージをロードしてくれるので、use LinkList; は不要になります。

コンストラクタ new では、スーパークラスの new を呼び出してインスタンスを生成し、FixedList で使用するインスタンス変数 limit と size を初期化します。limit は要素数の上限値を表していて、引数 $limit で指定します。size は連結リストに格納されている要素数を格納します。

insert_nth ではメソッド is_full を呼び出してリストが満杯かチェックします。そうであれば $Cell::nil を返します。空きがあれば、スーパークラス LinkList の insert_nth を呼び出してデータ $x を挿入します。挿入できない場合は $Cell::nil を返すので、メソッド null でチェックします。$r のデータ型はセルとは限らないので、関数の形式で null を呼び出します。挿入できた場合は size を +1 して、挿入したデータ $r を返します。

delete_nth の場合、スーパークラス LinkList のメソッド delete_nth を呼び出します。削除できない場合は $Cell::nil を返すので、メソッド null でチェックします。削除できた場合は size を -1 して、削除したデータ $r を返します。これで、連結リストに格納される要素数を管理することができます。

簡単な実行例を示しましょう。

リスト : 簡単なテスト (testfixed.pl)

use strict;
use warnings;
use FixedList;

my $xs = FixedList->new(8);

foreach my $i (1..9) {
    $xs->insert_nth(0, $i);
    $xs->print_list();
}
print $xs->is_full(), "\n";

foreach my $i (1..4) {
    $xs->delete_nth(0);
    $xs->print_list();
}
print $xs->is_full(), "\n";

foreach my $i (1..5) {
    $xs->insert_nth(0, $i);
    $xs->print_list();
}
print $xs->is_full(), "\n";
$ perl -I. testfixed.pl
(1)
(2, 1)
(3, 2, 1)
(4, 3, 2, 1)
(5, 4, 3, 2, 1)
(6, 5, 4, 3, 2, 1)
(7, 6, 5, 4, 3, 2, 1)
(8, 7, 6, 5, 4, 3, 2, 1)
(8, 7, 6, 5, 4, 3, 2, 1)
1
(7, 6, 5, 4, 3, 2, 1)
(6, 5, 4, 3, 2, 1)
(5, 4, 3, 2, 1)
(4, 3, 2, 1)

(1, 4, 3, 2, 1)
(2, 1, 4, 3, 2, 1)
(3, 2, 1, 4, 3, 2, 1)
(4, 3, 2, 1, 4, 3, 2, 1)
(4, 3, 2, 1, 4, 3, 2, 1)
1

正常に動作していますね。このように LinkList を継承することで、FixedList を簡単にプログラムすることができます。

●多重継承

次は、多重継承について説明します。簡単な例題として、Foo と Bar の2つのクラスを継承するクラス Baz を考えてみましょう。まず、Foo と Bar を定義します。

リスト : クラス Foo の定義

package Foo;

sub new {
    my ($type, $a) = @_;
    my $obj = {a => $a};
    bless $obj, $type;
    $obj;
}

sub get_a {
   my $obj = shift;
   $obj->{'a'};
}

sub method_1 {
    print "Foo::method_1\n";
}
リスト : クラス Bar の定義

package Bar;

sub new {
    my ($type, $b) = @_;
    my $obj = {b => $b};
    bless $obj, $type;
    $obj;
}

sub get_b {
    my $obj = shift;
    $obj->{'b'};
}

sub method_1 {
    print "Bar::method_1\n";
}

クラス Foo にはインスタンス変数 a とアクセスメソッド get_a、クラス Bar にはインスタンス変数 b とアクセスメソッド get_b が定義されています。そして、両方のクラスともメソッド method_1 が定義されています。Foo と Bar を継承するクラス Baz は、次のように定義されます。

リスト : クラス Baz の定義

package Baz;
our @ISA = ('Foo', 'Bar');

sub new {
    my ($type, $a, $b) = @_;
    my $obj = {a => $a, b => $b};
    bless $obj, $type;
    $obj;
}

配列 @ISA に Foo と Bar をセットします。これで Foo と Bar を継承することができます。さっそく実行してみましょう。

リスト : sample1402.pl

use strict;
use warnings;

#
# クラス定義は省略
#

package main;

my $o1 = Baz->new(10,20);

print $o1->get_a(), "\n";   # 10 を表示
print $o1->get_b(), "\n";   # 20 を表示
$o1->method_1();            # Foo::method_1 と表示
$ perl sample1402.pl
10
20
Foo::method_1

継承したメソッド get_a, get_b を呼び出すことができるのは当然ですが、両方のクラスにある method_1 は、どちらが呼び出されるのでしょうか。表示された Foo::method_1 から、クラス Foo のメソッドが呼び出されたことがわかります。

このように、メソッドの検索は配列 @ISA の先頭から順番に行われ、最初に見つかったメソッドが実行されます。クラスの順番を逆にすると、今度は Bar::method_1 と表示されます。

では、Foo と Bar にスーパークラスが設定されている場合はどうなるのでしょうか。この場合、メソッドは「深さ優先」で探索されます。次の図を見てください。

クラス G は、クラス D, E, F を多重継承しています。D, E, F のスーパークラスはそれぞれA, B, C です。クラス G で @ISA = (D, E, F) と設定されているとすると、最初にクラス D のメソッドを探索します。次は深さ優先で探索するので、クラス E ではなくクラス A を探索します。

このように、スーパークラスを優先して探索し、それでも見つからないときはクラス E を探索します。したがって、探索順序は「G → D → A → E → B → F → C」となるのです。上図を経路と考えれば、まさに深さ優先探索そのものですね。

●多重継承の問題点

多重継承を使う場合、異なる性質や機能を持つクラスを継承することがあります。たとえば、クラス Foo にはメソッド method_a があり、クラス Bar にはメソッド method_b があるとしましょう。この 2 つのメソッドはまったく異なる働きをします。ここで、method_a は変数 x を使っていて、method_b も変数 x を使っていると、多重継承で問題が発生します。

クラス Foo と Bar を多重継承してクラス Baz を作成した場合、クラス Baz のインスタンスには変数 x がひとつしかありません。メソッド method_a と method_b はひとつしかない変数 x を使うことになります。この場合、どちらかのメソッドは正常に動作しないでしょう。これでは多重継承する意味がありません。これが多重継承の問題点です。

このように、多重継承はどんなクラスでもできるというわけではありません。同名のインスタンス変数を持つクラスは多重継承できないと考えた方がよいでしょう。それから、多重継承にはもうひとつ問題点があります。それはクラスの階層構造が複雑になることです。

単一継承の場合、クラスの階層は木構造になりますが、多重継承ではグラフになります。木構造の場合、クラスの優先順位は簡単にわかりますが、グラフになると優先順位を理解するのは難しくなります。多重継承は強力な機能ですが、使うときには十分な注意が必要なのです。

ちなみにC++の場合、多重継承したクラスに同名のメソッドがある場合、どちらを呼び出すのか明確に指定しないとコンパイルでエラーとなります。またC++はメンバ変数も継承されるため、変数名の衝突も発生します。この場合も、どちらの変数を使用するのか明確に指定しないとコンパイルでエラーが発生します。

このほかにも、多重継承ではいろいろな問題が発生するため、それを解決するためにC++ではいろいろな機能が用意されています。ところが、それらの機能がC++をいっそう複雑な言語にしていると、M.Hiroi には思えてなりません。C++はコンパイラ型の言語で、なによりも効率を重視するため、Perl のようなインタプリタ型の言語よりも複雑な言語仕様になるのは避けられないのかもしれません。

●Mix-in

これらの問題を回避するため、インスタンス変数 (属性) を継承するスーパークラスはひとつだけに限定して、あとのスーパークラスはメソッド (実装) だけを継承するという方法があります。この方法を Mix-in といいます。

具体的には、インスタンス変数を定義せずにメソッドだけを記述したクラスを用意します。属性の継承は単一継承になりますが、実装のみを記述したクラスはいくつ継承してもかまいません。ひとつのクラスに複数の実装を混ぜることから Mix-in と呼ばれています。

なお、Mix-in は特別な機能ではなく、多重継承を使いこなすための方法論にすぎません。多重継承を扱うことができるプログラミング言語であれば Mix-in を行うことが可能です。ちなみに、この Mix-in という方法を言語仕様に取り込んだのが Ruby です。

Perl は多重継承をサポートしているので、Mix-in を利用することができます。下図を見てください。


        図 : Mix-in

クラス C はクラス B を継承していて、そこにクラス Mixin A が Mix-in されています。クラス D もクラス B を継承していますが、Mix-in されているクラスは Mixin B となります。

多重継承の問題点は Mix-in ですべて解決できるわけではありませんが、クラスの階層構造がすっきりとしてわかりやすくなることは間違いありません。Mix-in は多重継承を使いこなす優れた方法だと思います。

●Enumerable

それでは Mix-in の例題として、クラス Enumerable を作ってみましょう。配列、ハッシュ、連結リスト、二分木のように、複数の要素を格納するデータ型を「コレクション (collection)」とか「コンテナ (container)」と呼びます。Enumerable はコレクションに高階関数 (メソッド) を Mix-in します。これは Ruby のモジュール (Mix-in 用のクラス) Enumerable を参考にしました。追加するメソッドを下表に示します。

表 : Enumerable のメソッド
名前機能
$obj->member($func)$func が真となる要素を返す
$obj->position($func)$func が真となる要素の位置を返す
$obj->count($func)$func が真となる要素の個数を返す
$obj->mapcar($func)要素に $func を適用した結果をリストに格納して返す
$obj->filter($func)$func が真となる要素をリストに格納して返す
$obj->fold($func, $init)すべての要素を $func を用いて結合した結果を返す

メソッド member は $func が真となる要素を見つけた場合、リスト (1, 要素) を返します。見つからない場合はリスト (0, 0) を返します。あとは特に難しいところはないと思います。

プログラムは次のようになります。

リスト : Mix-in 用 Enumerable.pm

use strict;
use warnings;

package Enumerable;

# 探索
sub member {
    my ($obj, $func) = @_;
    my $item;
    $obj->each(
	sub {
	    my $x = shift;
	    if ($func->($x)) {
		$item = $x;
		goto FIND;
	    }
	});
    return (0, 0);
  FIND:
    (1, $item);
}

# 位置を返す
sub position {
    my ($obj, $func) = @_;
    my $i = 0;
    $obj->each(
	sub {
	    my $x = shift;
	    goto FIND if $func->($x);
	    $i++;
	});
    return -1;
  FIND:
    $i;
}	

# 個数を数える
sub count {
    my ($obj, $func) = @_;
    my $c = 0;
    $obj->each(
	sub {
	    my $x = shift;
	    $c++ if $func->($x);
	});
    $c;
}

# マップ
sub mapcar {
    my ($obj, $func) = @_;
    my $a = [];
    $obj->each(
	sub {
	    my $x = shift;
	    push @$a, $func->($x);
	});
    $a;
}

# フィルター
sub filter {
    my ($obj, $func) = @_;
    my $a = [];
    $obj->each(
	sub {
	    my $x = shift;
	    push @$a, $x if $func->($x);
	});
    $a;
}

# 畳み込み
sub fold {
    my ($obj, $func, $a) = @_;
    $obj->each(
	sub {
	    my $x = shift;
	    $a = $func->($a, $x);
	});
    $a;
}

1;

クラス Enumerable は Mix-in を前提としているので、コンストラクタ new の定義は不要でメソッドだけを定義します。コレクションの要素は高階関数 each で取り出します。each は Mix-in するクラスで定義されているものとします。つまり、高階関数 each を定義すれば、どんなクラスでも Enumberable を Mix-in することができるわけです。

●Perl の goto

ここでメソッド member と position で使用している goto について説明します。Perl には 3 種類の goto があります。

1. goto LABEL
2. goto EXPR
3. goto &NAME

今回説明するのは 1 の形式です。goto は指定した LABEL に無条件ジャンプする機能ですが、C言語と違って関数の外側にもジャンプすることができます。LABEL はダイナミックスコープで管理されていて、goto は直近の LABEL にジャンプします。

たとえば、member を呼び出すと、メソッド each が呼び出され、その中で無名関数が呼び出されます。この中で goto FIND; を実行すると、関数の呼び出し履歴をたどり、ラベル FIND が定義されている関数を探します。この場合、member の中に FIND があるので、そこにジャンプします。つまり、無名関数と each の処理を抜けて、制御は member の FIND 以降の処理に移るわけです。

簡単な例を示しましょう。

リスト : goto の使用例 (sample1403.pl)

use strict;
use warnings;

sub baz {
    print "call baz\n";
    goto EXIT;
}

sub bar {
    print "call bar\n";
    baz();
    print "bar end\n";
}

sub foo {
    print "call foo\n";
    bar();
    print "foo end\n";
    return 1;
  EXIT:
    print "foo exit\n";
    0;
}

foo();
$ perl sample1403.pl
call foo
call bar
call baz
foo exit

このように、goto を使って関数 bar から関数 foo のラベル EXIT へジャンプすることができます。

ただし、goto を多用すると処理の流れがわからなくなる、いわゆる「スパゲッティプログラム」になってしまいます。Perl は eval と die を使った例外処理もあるので、goto を使わなくてもプログラムすることは可能です。まあ、今回は関数内にある無名関数から同じ関数内のラベルにジャンプする処理なので、goto を使ってもプログラムはそれほどわかりにくくならないでしょう。

●Enumerable を Mix-in する

それでは実際に Enumerable を Mix-in してみましょう。新しいクラス List を定義して、LinkList と Enumerable を多重継承します。次のリストを見てください。

リスト : クラス List の定義 (testenum.pl)

use strict;
use warnings;
use LinkList;
use Enumerable;

package List;
our @ISA = ('LinkList', 'Enumerable');

sub new {
    List->SUPER::new();
}

@ISA に LinkList と Enumerable をセットします。これで LinkList から属性とメソッドを継承し、Enumerable からメソッドを継承する、つまり Enumerable を Mix-in することができます。コンストラクタ new は簡単で、スーパークラスのコンストラクタ、この場合は LinkList の new を呼び出すだけです。

それでは簡単なテストを行ってみましょう。

リスト : 簡単なテスト (testenum.pl)

package main;

my $xs = List->new();
foreach my $i (1 .. 8) {
    $xs->insert_nth(0, $i);
}
$xs->print_list();
my ($r, $n) = $xs->member(sub {$_[0] == 1}); 
print "$r $n\n";
($r, $n) = $xs->member(sub {$_[0] == 3}); 
print "$r $n\n";
($r, $n) = $xs->member(sub {$_[0] == 8}); 
print "$r $n\n";
($r, $n) = $xs->member(sub {$_[0] == 9}); 
print "$r $n\n";

print $xs->position(sub {$_[0] == 1}), "\n";
print $xs->position(sub {$_[0] == 3}), "\n";
print $xs->position(sub {$_[0] == 8}), "\n";
print $xs->position(sub {$_[0] == 9}), "\n";

print $xs->count(sub {$_[0] == 1}), "\n";
print $xs->count(sub {$_[0] == 3}), "\n";
print $xs->count(sub {$_[0] == 8}), "\n";
print $xs->count(sub {$_[0] == 9}), "\n";

my $a = $xs->mapcar(sub {my $x = shift; $x * $x});
print "@$a\n";
$a = $xs->filter(sub {my $x = shift; $x % 2 == 0});
print "@$a\n";
print $xs->fold(sub {$_[0] + $_[1]}, 0), "\n";
$ perl -I. testenum.pl
(8, 7, 6, 5, 4, 3, 2, 1)
1 1
1 3
1 8
0 0
7
5
0
-1
1
1
1
0
64 49 36 25 16 9 4 1
8 6 4 2
36

正常に動作していますね。複数のクラスで共通の操作 (メソッド) を定義したい場合、Mix-in はとても役に立ちます。

●間接記法

ところで、Perl には多種多用な表記方法が用意されていますが、メソッドの呼び出しにも矢印記法だけではなく「間接記法」という別の方法があります。これは次のように、オブジェクト名やクラス名よりも先にメソッド名を書く方法です。

METHOD CLASS_OR_INSTANCE LIST

メソッド名とクラスまたはインスタンスのあいだにカンマ ( , ) を入れてはいけません。引数を複数渡す場合はカンマで区切ります。具体的には、次のようになります。

my $o1 = new Baz 10, 20;

引数を括弧で囲んでもかまいません。

$o1 = new Baz(10, 20);

こうすると、C++でよく出てくる表記法と同じになります。ただし、間接記法は矢印記法より曖昧であることに注意してください。次の例を見てください。

method $obj->{'key'};

method はメソッドですが、インスタンスは $obj か $obj->{'key'} のどちらになるのでしょう。この場合、Perl は $obj をインスタンスと判断し、method $obj を実行します。そして、その結果はハッシュのリファレンスでなければいけません。$obj->{'key'} のメソッドを呼び出す場合は、次のように矢印記法を使った方がいいでしょう。

$obj->{'key'}->method();

こちらの方がはっきりとわかりますね。


初版 2015 年 5 月 17 日
改訂 2023 年 3 月 19 日

Copyright (C) 2015-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | Perl | NextPage ]