習うより慣れろなPerlのOOP入門

入門記事書けるようなレベルではありません*1ので、タイトルは釣りです。

諸事情により、Perlのモダンな機能の予備知識0の状態で、突如分けわからないOOPPerlの開発をちょこっとしたので、その知見をまとめたものです。オブジェクト指向のベーシックな概念については省きます。「PerlOOPってどうやってやるの?」という人の役に立ったら御の字です。

リファレンス

まず、これを何となく理解していないと、どうしようもなさそうです。

導入

Perlのリファレンス(参照)というのは、他の言語と同様、ポインタ的なアレのことを意味してます。Perlは、よくよく考えてみると、

%a = (foo => 'bar');
%b = %a;
$a{foo} = 'poo';
print $b{foo}; # 'bar'

普通にやると、実体が代入される(ディープコピー)のですね。でも実際問題、参照使わないと高度なプログラミングできないじゃん、ということで、Perl5辺りで導入されたらしいです。

では先のプログラムを参照で書き直してみます。

$a = {foo => 'bar'};
$b = $a;
$a->{foo} = 'poo';
print $b->{foo}; # 'poo'

参照っぽい感じになりました。

リファレンスの作り方

基本的には、値の入った変数の前にバックスラッシュを付けるだけです。

@a = (1, 2);
$ref_of_array_a = \@a;
%a = (1, 2);
$ref_of_hash_a = \%a;

リテラルにバックスラッシュつければ、一々変数に代入しなくてもダイレクトにリファレンスを作れます。が、リストとハッシュは生成時に区別がつかないので*2、リファレンス生成専用の括弧があります*3。余談ですが、Perlでは=>はカンマと完全に同義です。

$ref_of_array_a = [1, 2];
$ref_of_hash_a = {1 => 2};

リファレンスの使い方

リファレンス*4スカラー的に利用されます。なので、スカラーでも配列でもハッシュでも何でも、リファレンスになったらスカラーとして扱われます。

リファレンスの実体にアクセス(デリファレンス)する時は、リファレンスにシジル($, @, %とかのアレ)をくっつけます。また、アロー演算子->でもデリファレンスできます。ブロック{}で囲むテクニックもあります。

%a = ('foo' => 'bar');
# ハッシュのリファレンス
$a = \%a;
# シジルによるデリファレンス
%b = %$a;
print $b{foo}; # bar
# 一気にシジルでデリファレンス
print $$a{foo};
# アロー演算子によるデリファレンス
print $a->{foo}
# ブロック(中カッコ)を使う
sub print_scalar_ref { print ${shift()}; }
print_scalar_ref \10;

上記はハッシュの場合のサンプルコードになりますが、配列の場合もサブルーチンの場合も似た感じです。アロー演算子でのデリファレンスの場合、配列の場合は$ref->[index]、サブルーチンの場合は$ref->(args)です。また、アロー演算子はネストしている時は省略出来たりします(例:$a->[0][0]$h->{foo}{bar})。

リファレンスが使えるようになって、無名関数が使えるようになって、クロージャが使えるようになると、夢が広がりんぐですね。

sub get_counter {
    my $count = 0;
    sub { $count++; }
}
my $counter = get_counter;
say &$counter; # 0
say &$counter; # 1

クラス、のまえにモジュール

Perlのクラスはモジュールに毛が生えた感じです。なので、OOP導入以前からある普通のモジュールの作り方を知らないと、足元がぐらついて理解が困難になります。

と言いつつ私自身ちゃんと理解していないので、すごく適当なサンプルだけ載せます。

まずは、モジュールMyModule.pm。モジュールは戻り値でロード成功か判定をするようなので、末尾に1;を書くのが定石です。また、モジュールをuseした時に、モジュール名なしにサブルーチンが使えるようになる事がありますが、犯人は@EXPORTです。

#!/usr/bin/env perl
package MyModule;
use strict;
use warnings;
use utf8;

our @EXPORT = qw/my_sum/;

sub sum {
    my $r = 0;
    $r += $_ foreach (@_);
    $r;
}

sub my_sum { sum @_; }

1;

以下は使用例です。モジュールの中身にはダブルコロン::演算子でアクセス出来ます。

#!/usr/bin/env perl
package MyModule;
use strict;
use warnings;
use utf8;
use MyModule;

my @list = (1, 2, 3, 4);

print my_sum(@list), "\n";
print MyModule::sum(@list), "\n";

クラス

ようやくクラスです。まずは、百聞は一見にしかずということで、サンプルコードを載せておきます。

クラスMyClass.pm。

#!/usr/bin/env perl
package MyClass;
use strict;
use warnings;
use utf8;

sub subroutine_sum {
    my $r = 0;
    $r += $_ foreach (@_);
    $r;
}

sub class_method_sum {
    my $class = shift;
    subroutine_sum @{shift()};
}

sub instance_method_sum {
    my $self = shift;
    subroutine_sum @{$self->{list}};
}

sub new {
    my $class = shift;
    my $list = shift;
    bless {list => $list}, $class;
}

1;

使う側。

#!/usr/bin/env perl
package MyModule;
use strict;
use warnings;
use utf8;
use MyClass;

my @list = (1, 2, 3, 4);
print MyClass::subroutine_sum(@list), "\n";
print MyClass->class_method_sum(\@list), "\n";
print MyClass->new(\@list)->instance_method_sum, "\n";

ポイントは、サブルーチンの呼び出し方です。モジュール内のただのサブルーチンsubroutine_sumにはダブルコロン演算子でアクセスしていますが、それ以外ではアロー演算子が使われています。

Perlではアロー演算子でサブルーチンを呼ぶと、そのサブルーチンの第一引数にレシーバ*5 がセットされる仕組みとなっています。そのため、メソッド側を見てみると、

sub class_method_sum {
    my $class = shift;
    ...

sub instance_method_sum {
    my $self = shift;
    ...

として第一引数を取り出しているわけです。

クラスメソッドとインスタンスメソッドの違いは、レシーバの違いでしかありません。もっと言えば、サブルーチンとクラスメソッドとインスタンスメソッドの違いは、レシーバが自動的に引数に渡るかの違いでしか無いので、手動で第一引数にレシーバをセットすれば、ダブルコロンでもクラスのメソッドが呼び出せます。この辺りの仕組みはPythonと似ていますね。

もう一つのポイントはコンストラクタ相当のクラスメソッドnewの最後にあるbless関数です*6。メンバをハッシュで用意しておいて、祝福してやると、何故かモジュールのサブルーチンを持ったオブジェクトに変身する、という仕組みになっています*7

後は見よう見まねで何とかなるでしょう(棒

おしまい。

*1:型グロブって何それレベルです。「初めてのPerl」8章レベルです。それでも見よう見まねでOOPは何とかなりました。

*2:しかもよく使われるので

*3:正確には無名配列とか無名ハッシュとか呼ばれるらしいです

*4:正確には「リファレンス値」

*5:'foo->bar'のfooがレシーバ

*6:blessというのは「神の祝福」的な意味合いを持つ英単語です

*7:自分でも何言ってるか分かりませんが、そんな感じでフィーリングで理解してれば十分だと思います