習うより慣れろなPerlのOOP入門
入門記事書けるようなレベルではありません*1ので、タイトルは釣りです。
諸事情により、Perlのモダンな機能の予備知識0の状態で、突如分けわからないOOPなPerlの開発をちょこっとしたので、その知見をまとめたものです。オブジェクト指向のベーシックな概念については省きます。「PerlでOOPってどうやってやるの?」という人の役に立ったら御の字です。
リファレンス
まず、これを何となく理解していないと、どうしようもなさそうです。
導入
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。
後は見よう見まねで何とかなるでしょう(棒
おしまい。
参考文献
- http://d.hatena.ne.jp/perlcodesample/20100930/1278596435
- http://www.slideshare.net/KondoYoshiyuki/yapc2012-20120929
- http://tech.bayashi.jp/archives/entry/perl/2009/002589.html
- http://www.geocities.jp/ky_webid/perl5/037.html
- http://perldoc.jp/docs/perl/5.18.1/perlmod.pod
- http://www.rwds.net/kuroita/program/Perl_oo.html