Perlのオブジェクト指向をつかんだ

経緯

2013年の春にふと「よし、プログラミングやろう」と決めてその夏にYAPC::Asiaに参加して、その直後に買ったのが以下の本。

すぐわかる オブジェクト指向 Perl

すぐわかる オブジェクト指向 Perl

たしか同YAPCDeNAの人が大規模新人研修に関する発表をしていて()、その最後のほうでお勧め入門書ベスト5みたいなやつを紹介していたのだけど、それがよくある特定の選者が一括で選ぶようなものではなく、研修を受けた側の新人さんたちに聞いたアンケートから抽出されたというリアルなもので、そこに含まれているのを見て興味を持った。

で、先日ようやくそれを読み終わって出た感想。


かるくタイポしてるが、

s/年前/年/

実感的にはほんとに「ようやくつかんだ〜」という感じだった。

オブジェクト指向というと、よく「理解は容易だが運用は難しい」という、なんだかスクラム実践入門みたいなことを言われるわけだが、ぼくはその前者の「理解」の段階だけで3年かかってしまった。

まあ、3年ずっとべったり学習し続けてその間わからなかった、ということでもないのだが(読んでない時間の方が多い)、ともあれ、たしかにわかってみると(あるいはそのような気になってみると)「理解は容易」と言われるのもわかる気はする。シンプルといえば大変シンプルなものである。

またオブジェクト指向といえば、「データと処理をセットで扱うものである」なんていうこともよく言われる。これもとくに理解してない段階で聞くと「はあ、そんなものですか」という感じだが(日本語としては理解できる、というか)、今になって思えば、その辺に関する「わかる/わからない」を分けるのは、概念を聞いて理解できるかどうかということではなく、具体的なコードをどれだけ見たか、書いたか、実行したかによって変わってくるように思える。
つまり、パッと聞いてわかるとかそういうものではなさそう。(あくまで入門者に関して言えることだが)

ひと口にオブジェクト指向と言っても、JavaRubyPerlのそれぞれでは、元になる概念に共通する面はあっても、やはりコード上ではそれなりに違いがあるだろう。
そこには、単に構文上の違いだけではなく、お作法というか慣習的な面で、「こうするのがヨイとされている」みたいな違いもあるはずで、だから結局は自分が普段よく使う言語を通してそれを学ぶのが、一番の理解の近道になるのではないかとも感じる。

冒頭の本の話に戻ると、これはけっこうなページ数と文章量があるうえ、大判のせいかやけに重たいので(質量が)、最後まで読むのはけっこう骨が折れた。
ちょっと時間ができたからといって、棚からそれを出すのも大変だし、開いて読むにもちょっとしたスペースが必要だしで、気持ちの面である程度余裕がないと「さあ、読もう」という感じになれない。

もしこれが多少なり素地のある人なら、自分のわからない/知らない部分だけを補う用途で日常的に付き合えるかもしれないが、ぼくの場合はそれこそある程度べったり読み込まないと解読できない内容だったので、その「腰を落ち着けて取り組む機会」をなかなか作れず挫折していた、というのもある。

ちなみに、同書の紹介としては小飼弾さんの以下が大変参考になって、コメント欄では著者の深澤さんもいろいろ述べておられて面白いのだけど、

404 Blog Not Found:$this->get if $you->learn(slow) - 書評 - すぐわかるオブジェクト指向Perl

そこでも言われているように、ほんとに「じっくり、ゆっくり、時間をかけて」という感じで読み継いだ。

じつは同書の前半には、非オブジェクト指向ネタというか、リファレンスに関する説明がみっちり載っていて、購入してから1〜2年ほどは、どちらかというとそのリファレンスまわりの理解を深めるために読むことが多かった。

というか、もちろん最大の目的はその時点でもオブジェクト指向だったので、そっちも読もうとはするのだけど、やはりその辺ってそこまでの前半部分を読んでいる前提で解説されているので、後半だけ読んでもまったく頭に入らず、それで前半に戻ってリファレンスのところなどを一生懸命読み返す、というのがしばらく続いた。

ちなみに、そのリファレンスまわりの解説について言えば、本書ほど丁寧にPerlのリファレンスについて教えてくれる入門書はない(おそらく今後も)だろうと思っている。
もし今つまづいている人がいたらお勧めしたい。

さて、とはいえそんな調子でのったらのったら読んでいるので、後半のオブジェクト指向にはいつまでも入れず、また普段からあまり余裕がなくて本自体にもなかなか触れられず、触れられないうちに前に読んだところも忘れて、だからようやく読み返したときにはまた最初のほうから読み直して……とかやってるうちに「もうこれ、最後まで読み切ることはないかもなあ」という諦め気分だったのだけど、なぜか今年の夏に入ったあたりで、「やっぱり……次はオブジェクト指向だな」と思った。

というのも、オブジェクト指向を多少なり理解していないことには、読めない他人のコードというのが多すぎると感じたのだ。
これは少し前にC言語の学習を始めたことや、シェルスクリプトLinuxコマンドを学びはじめたことにも近い理由であって、言い換えれば「みんなこの言葉を喋っているので自分も学ばないと彼らが何言ってるのかいつまでもわからないぞ」という焦りの気分でもある。

ということで、じゃあどこから再入門するかと思ったとき、半ば挫折しかけてはいるとはいえ、とりあえず途中までは読んでいる同書に再度取り組みはじめたら、ある程度心が決まっていたせいか最後まで理解しつつ読み終えることができたという話。

概念

ということで、以下ではここまでに理解した内容を簡単にまとめてみたい。

まず、オブジェクト指向を一言でいえば、上でもちらっと書いたように「データ(属性・プロパティ)と処理(振る舞い・メソッド)をくっつけたオブジェクトなるものを使っていろいろやる」ということになる。
この概念が、この話の一番の軸・前提になる。

また、Perlを使ったオブジェクト指向においては、おもに前者のデータをリファレンスで、後者の処理をサブルーチンで表す。
で、作ったそれらをbless関数でくっつけるとオブジェクトができる。

ここで出来たオブジェクトというのは、鋳型(いがた)のようなもので、鯛焼きやタコ焼きで使うような、型のある鉄板みたいなもの。
と同時に、プログラムの中で実際にあれこれやるのはその型じたいではなく、型をもとに作られた鯛焼きなりタコ焼きなりの1個1個。

で、前者の鋳型みたいなものをクラスと言い、その鋳型から出てきた後者の1個1個の具体的な実体をインスタンスと言う。

そしてその1個1個に対して何かを入れたり、そこから何かを引き出したり、それに何かをやらせたりしていく中でいろいろやるのがオブジェクト指向プログラミング。(雑になってきた)

実例

では概念的な話はそのぐらいにして、以下ではPerlによるそれを実例で紹介してみる。
むちゃくちゃベタだけど、動物ネタで。

こんな感じの構成を考える。

Animal
    ├─ lib
    │    ├─ Animal.pm
    │    └─ Animal
    │           ├─ Dog.pm
    │           └─ Cat.pm
    └─ main.pl

これはざっくり言うと、Animalという型が元にあって、それを継承したDog, Catという子クラスを配置したモデル。

いまあっさり「子クラス」と書いたが、そのときAnimal.pmの方は「親クラス」ということになる。

が、それは説明が簡単なのでそのように言っているものの、実際にはAnimalとDog/Catの関係は「親/子」とか「主/従」のような上下関係というより、「抽象/具体」という対照的な(お互いに呼応した)関係だと考えたほうが腑に落ちる。
これは冒頭に挙げた深澤さんの本でそのように説明されていて、なるほどと思った。

では具体的なコード。

まずAnimal.pm。

package Animal;
use strict;
use warnings;

sub new {
    my $class = shift;

    my %self = @_;
    $self{name} //= 'No name';
    $self{age} //= 0;
    $self{sound} //= '(silence)';

    bless \%self, $class;
}

sub speak {
    my $self = shift;
    return $self->{ sound };
}

1;

newメソッドの中でプロパティ(属性)を設定している。この「プロパティ」というのは他にも「アトリビュート」とか「フィールド」とも呼ばれるようだが(後者はJava用語っぽい)ここではプロパティと呼んでおく。

speakというのはサブルーチンだが、オブジェクト指向の際にはメソッドと呼ぶらしい。

上のほうで「データと処理」と表現したが、先のプロパティがデータにあたり、このメソッドが処理にあたる。

次に、Animal.pmを継承したDog.pm。

package Animal::Dog;
use strict;
use warnings;
use parent 'Animal';

sub speak {
    my $class = shift;
    return 'Bow!';
}

sub move {
    my $class = shift;
    return 'Jump!';
}

1;

継承すると、親クラスのAnimalの方で定義しているメソッド(newやspeak)を子クラスでも使えるようになる。だからここではnewメソッドを作らない。

なるほど、これなら重複する内容を書かなくて済むわけで、継承などをしない手法に比べて大きなメリットだと感じる。

その上で、ここではメソッドのオーバーロード(上書き)というやつで、speakメソッドをAnimal.pmのそれからちょっと書き換える。
Animalのspeakメソッドではプロパティ内のsound要素を呼び出していたが、Dogの方ではそうせずに独自の 'Bow!' を返すことにする。

また、Animalの方にはなかったmoveメソッドも独自に追加した。

もう一個、Animal.pmを継承したCat.pmを作ってみる。

package Animal::Cat;
use strict;
use warnings;
use parent 'Animal';

sub new {
    my $class = shift;
    my %self = @_;
    $class->SUPER::new(%self, sound => 'Nyaa..');
}

1;

こちらではnewを少し書き換える。
Dog.pmの方ではspeakメソッドを直接変更したが、ここではnewの方からsoundの値を書き換えて鳴き声を変えてみる。

この書き換えはあまりいい例じゃない気もするが(有効性を感じられないというか)、まあ、子クラスから親クラスのメソッドを呼び出す例として面白いので。

最後に、それらを実行するmain.pl。

#!/usr/bin/env perl
#
# main.pl

use strict;
use warnings;
use feature 'say';
use lib './lib';
use Animal;
use Animal::Dog;
use Animal::Cat;

my $animal = Animal->new(name => 'foo', age => 100);
say "Animal's name:\t".$animal->{ name };
say "Animal's age:\t".$animal->{ age };
say "Animal speaks:\t".$animal->speak;
say '---';
my $dog = Animal::Dog->new(name => 'Pochi', sound => 'Vow!');
say "Dog's name:\t".$dog->{ name };
say "Dog's age:\t".$dog->{ age };
say "Dog speaks:\t".$dog->speak;
say "Dog moves:\t".$dog->move;
say '---';
my $cat = Animal::Cat->new(name => 'Tama', age => 3);
say "Cat's name:\t".$cat->{ name };
say "Cat's age:\t".$cat->{ age };
say "Cat speaks:\t".$cat->speak;

結果。

Animal's name:	foo
Animal's age:	100
Animal speaks:	(silence)
---
Dog's name:	Pochi
Dog's age:	0
Dog speaks:	Bow!
Dog moves:	Jump!
---
Cat's name:	Tama
Cat's age:	3
Cat speaks:	Nyaa..

OK。

もう一個、オブジェクト指向では多重継承という手法がよく話題になるようで、ここではDogとCatの親クラスはAnimalのみだけど、複数の親からメソッドを継承したい場合にはどうするか、という話。

たとえば、「動物の中の何か」であると同時に、「その物体は何色か」という要素を規定するクラスをDogやCatで共通して流用したい場合。

ひとつの方法として、現在Animalから諸メソッドを継承しているように、そういった別の親クラス(仮にColor.pmとする)を継承しても良いが、多重継承というのはいろいろ煩雑になる面があるらしく(雑な説明だが)、それを避けたい場合は普通にuseする。

まずColor.pmを作る。

package Color;
use strict;
use warnings;

sub color {
    my $self = shift;
    my $color = shift;
    if ($color) {
        return $color;
    } else {
        return 'brown';
    }
}

1;

引数を与えたらその色、与えなければ「brown」ということにした。
(たしか「続・初めてのPerl」でこんな例があった気がしたので借りた)

ここではこのColor.pmをAnimal.pmと同階層に置いておく。
こんな感じ。

Animal
    ├─ lib
    │    ├─ Animal.pm
    │    ├─ Color.pm # <= 追加
    │    └─ Animal
    │           ├─ Dog.pm
    │           └─ Cat.pm
    └─ main.pl

で、Animal.pmを少し書き換え。

package Animal;
use strict;
use warnings;

sub new {
    my $class = shift;

    my %self = @_;
    $self{name} //= 'No name';
    $self{age} //= 0;
    $self{sound} //= '(silence)';

    bless \%self, $class;
}

sub speak {
    my $self = shift;
    return $self->{ sound };
}

# colorメソッドを追加。
sub color {
    my $self = shift;
    my $color = shift;
    return Color->color($color);
}

1;

このままDog, Catの方はとくに触らずに、main.plでColor.pmをuseして、それぞれのcolorを呼び出してみる。

#!/usr/bin/env perl
#
# main.pl

use strict;
use warnings;
use feature 'say';
use lib './lib';
use Animal;
use Animal::Dog;
use Animal::Cat;
use Color; # <= 追加

my $animal = Animal->new(name => 'foo', age => 100);
say "Animal's name:\t".$animal->{ name };
say "Animal's age:\t".$animal->{ age };
say "Animal speaks:\t".$animal->speak;
say '---';
my $dog = Animal::Dog->new(name => 'Pochi', sound => 'Vow!');
say "Dog's name:\t".$dog->{ name };
say "Dog's age:\t".$dog->{ age };
say "Dog speaks:\t".$dog->speak;
say "Dog moves:\t".$dog->move;
say "Dog's color:\t".$dog->color; # <= 追加
say '---';
my $cat = Animal::Cat->new(name => 'Tama', age => 3);
say "Cat's name:\t".$cat->{ name };
say "Cat's age:\t".$cat->{ age };
say "Cat speaks:\t".$cat->speak;
say "Cat's color:\t".$cat->color('white'); # <= 追加

結果。

Animal's name:	foo
Animal's age:	100
Animal speaks:	(silence)
---
Dog's name:	Pochi
Dog's age:	0
Dog speaks:	Bow!
Dog moves:	Jump!
Dog's color:	brown
---
Cat's name:	Tama
Cat's age:	3
Cat speaks:	Nyaa..
Cat's color:	white

OK。Dog, Catの最後にカラーが追加された。

このように、「多重継承したいけどしたくないので別の方法でいい感じにいろんなクラスを取り込みたい」みたいな時のこういう方法をMix-inというらしい。
いや、厳密に定義すれば上のような方法をそうは言わないかもしれないが(よく知らない)、まあ理念というか目的としては似たようなものかと捉え中。

参考書籍

ちなみに、そのMix-inを含めてオブジェクト指向の基礎みたいなところについては、まつもとゆきひろさんの以下の本がとても参考になった。(まだ読み切れていないが入門者向けっぽいわりにすごい濃い)

また、同書を紹介してくれたのはYAPC::AsiaやPerl入学式で時々会う人で、「あの本はオブジェクト指向の説明がすごくわかりやすかったな」と、たしか一昨年(2014年)のYAPC::Asiaで会の終了後に会場下のHUBで飲んでいるときに教えてもらったのでそのまま購入&読んだという経緯がある。
その節はありがとうございました。>その方

それから、上でもちらっと書いたとおりオブジェクト指向Perlの学習に際しては、冒頭の深澤さんの本の他、皆さんお馴染み『続・初めてのPerl』と、

続・初めてのPerl 改訂第2版

続・初めてのPerl 改訂第2版

@kaz_hiramatsuさんによる『雅なPerl入門 第3版』が参考になった。
kazhiramatsu.hatenablog.com

どちらかと言うと、『すぐわかるオブジェクト指向Perl』と『雅なPerl入門』で全貌をつかみつつ、『続・初めてのPerl』で脇を固める、みたいな読み方だったか。

まとめ・展望

ということで、本当はこのまま今度は上記のコードをMouseというモジュールを使って書き換えた版も復習がてら書いておきたかったのだけど、だいぶ長くなったのでひとまずここまで。

実際には、これらを踏まえた上で同内容をMouseで書き換えたら「なるほど、まあこの方がいろいろラクかもしれない」と思ったりしたので、そこまでを自分の理解のひとまとまり、と捉えてはいるのだけど。

あと、上では触れなかったがアクセサ(ゲッター&セッター)というやつに関する理解が薄いという自覚があり、かつたぶんカプセル化というやつを意識する上ではそれが重要だと思われるのだけど、その辺の理解度が低い段階で上のようなblessを使った方法でゴリゴリやっているといつまでもこの記事をUPできなそうなので、次にMouseの基礎的な使い方をまとめる際、あらためてその辺も勉強してみたい。

ひとまずのまとめとしては、やはりオブジェクト指向というのはどうもプログラマーの人たちにおける一種の共通言語のようになっている部分があるようで、その一部をカタコトながらも解読できつつあるのは喜ばしい。

同様に、サーバーまわりやSQL、あとちょっと外れたところで(というか)TeXなど*1、勉強したいことは山積みだが地道に触れていってみたい。

*1:自分の分野に近いので、というところもある