第32回 前回のソースの説明

さて、どこから説明したらいいものやら......
という感じなのですが、前回のソースの説明をしたいと思います。

前回のソースはもうひとつ窓を開けてもらって(別のブラウザとかで見てもらって)ここでは、部分的に説明していきたいと思います。


まずは、

new

 33   my $human = Human::new({'name' => 'ビンゴ',
 34                           'age'  => 1,
 35                           'bag'  => {}
 36                         });

このnew をしている部分からいきたいと思います。
new関数自体は次のようになっています。

  5   sub new {
  6     my $c = shift;
  7     bless $c;
  8   }

単に、受け取ったものをblessしているだけ。
つまり、受け取ったものに魔法をかけて、Humanパッケージの関数を使えるようにしているわけですね。


おっと!
そうそう、主人公をつくるためのパッケージは、Humanという名前にしました。
このHumanには主人公にできることがたくさん関数として書かれています。


では、newの引数になにを与えているかというと、無名ハッシュを与えています。
'bag'={} の部分は、多段ハッシュになっています。
これをnewに与え、$humanによりHumanパッケージの関数を呼べるようにします。

$human->push('やくそう');

お次は、

 38   $human->push('やくそう');

この部分を説明したいと思います。

push関数自体は、

 15   sub push {
 16     my $self = shift;
 17     my $item = shift;
 18     $self->{'bag'}->{$item}++;
 19   }

このようになっています。
注目すべきは、 16行目の $self です。
$selfに第1引数を受け取っています。
第1引数は、

 38   $human->push('やくそう');

の形で呼び出しているので、 $human です。  (※ ->で関数を呼ぶ方法を思い出してください)


$humanは、

 33   my $human = Human::new({'name' => 'ビンゴ',
 34                           'age'  => 1,
 35                           'bag'  => {}
 36                         });

のように作っています。
ですから、

$human = {'name' => 'ビンゴ',
          'age'  => 1,
          'bag'  => {}
         }

なのです。ただし、普通のハッシュと違うところは、このハッシュは、Humanパッケージの関数を呼べるのです。
そんな$humanを$selfに代入しているのが、16行目とういことになります。


17行目の、

 17     my $item = shift;

で、$itemに 'やくそう' が代入されています。


18行目の

 18     $self->{'bag'}->{$item}++;

が、ミソです。
ここで使われている -> は、両方ともデリファレンスの矢印です(多段ハッシュ)。
ハッシュにキーが登録されていない場合にいきなり++すると、値が1となります。
次回は、1を++するわけですから2になります。
次のソースを試してみてください。

use strict;

my $c = {};
$c->{'hoge'}++;

print $c->{'hoge'}, "\n";

$c->{'hoge'}++;
print $c->{'hoge'}, "\n";

これで、$selfの'bag'が示すハッシュの内容が変わりました!
ここで、$selfは$humanであったことを思い出しましょう!
$humanは、

$human = {'name' => 'ビンゴ',
          'age'  => 1,
          'bag'  => {}
         }

ですね。つまり、リファレンスなのです。
そうです。 $humanは、リファレンス渡しになっているのです。
ですから、$selfの内容をいじると、$humanの値もかわります。

 38   $human->push('やくそう');

こう書けば、$humanの内容が書き変わっているのです。
これは、JavaScriptJavaなどの他のオブジェクト指向言語のメソッドで、できることと同じです。
ただ、他の言語で this にあたるような部分を第1引数として受け取って $self に代入しているのです。


$human->get_old();

さて、なんでいっぱい説明のいる push関数から説明してしまったのでしょうか。

 43   $human->get_old();

の部分を見てみましょう。
get_old関数自体は、

 21   sub get_old {
 22     my $self = shift;
 23     $self->{'age'}++;
 24   }

このようになっていますね。
$selfには$humanが入っています。その$humanの年を+1しているのです。

$human->check_age();

check_age関数は受け取ったハッシュの'age'キー(年)を表示しているだけです。

 26   sub check_age {
 27     my $self = shift;
 28     print $self->{'age'}, '才',  "\n";
 29   }

43行目と44行目で理解を深める。

 43   $human->get_old();
 44   $human->check_age();

43行目のget_oldで $self->{'age'}++ されています。
$selfにはリファレンスが渡っているので、実質的に、
$human->{'age'}++ されたのと同じです。

とても信じられないと言うのなら、

 44   $human->get_old();
 45   print "-------------\n";
 46   print Dumper $human;
 47   print "-------------\n";
 48   $human->check_age();

とソースを書き換えて、$humanを表示させてみるとよいでしょう。
あ! Data::Dumperを使うので、

2   use Data::Dumper;

と 2行目に Data::Dumper; を付け加えることを忘れないでください。

$humanを表示させている部分だけをみてみる

$humanを表示させた部分を載せておきます

-------------
$VAR1 = bless( {
                 'bag' => {
                            '肉' => 1,
                            'やくそう' => 2
                          },
                 'name' => 'ビンゴ',
                 'age' => 2
               }, 'Human' );
-------------

このようになっています。
$humanをリファレンス渡しで push やら、 old_ageやらに渡しているので、いろいろ値が書き変わっているのがわかります。

簡単に書くと、

$VAR1 = bless ( {
                  $humanの内容
                }, 'Human' );

という形をしていますね。
本当にData::Dumperはすばらしいです。blessの第2引数に 'Human'が与えられている形をしていますね。
まったくそのまんまの通りです。 $humanは、Humanパッケージの関数が呼べることを、Data::Dumperは教えてくれているのです。


$selfで頑張ってメソッドを作成する

このように、Perlオブジェクト指向は、$selfに第1引数をもらうことにより記述していきます。
$selfという名前にこだわらず、変数名は何にしてもいいのですが、 $selfを使っているソースが非常に多いです。

newに別の内容の無名ハッシュを与えてみる

newに別の内容の無名ハッシュ、例えば、

{'name' => '木村',
 'age'  => 5,
 'bag'  => {}
}

といったものを与えて、

  package main;
  my $bingo  = Human::new({'name' => 'ビンゴ',
                           'age'  => 1,
                           'bag'  => {}
                         });

  my $kimura = Human::new({'name' => '木村',
                           'age'  => 1,
                           'bag'  => {}
                         });
 
  $bingo->push('やくそう');
  $kimura->push('肉');

といったように、別の持ち物を持たせていくことも可能です。
$bingoと$kimuraは別々のハッシュなので、ただ別々のハッシュの内容が変わっていくだけです。


まとめ

このように、Perlオブジェクト指向は、newにリファレンスを与えることからはじまり、
返ってきた内容を、 ○->hoge() というように -> によって知らないうちに第1引数となったモノを$selfで受け取り、その$selfの内容をがちゃがちゃ書き換えていくことにより、成り立っています。


$selfは他の言語の this みたいなものであると考えると非常にわかりやすいと思います。
逆に、他の言語で this がわからないなぁ という段階なら、 $self の考え方で パッと理解できるかもしれません。

this というのは、そのメソッドを呼んだやつ自身のことですね。
この自身という意味合いで、Perlでは、 $self が使われています(たぶん)。


それでは また〜

追記

参考

古い記事で、今よりももっと理解が曖昧だったときに書いた記事ですが、
参考までに以下の記事をあげて置きます。当時の私がどのように理解していったか、またどのように混乱していたかもわかると思います。

ビンゴ中西のほげほげKansai.pm第8回ミーティングに行ってきました@荷造り中
ビンゴ中西のほげほげPerlでのオブジェクト指向の前に
ビンゴ中西のほげほげPerlでオブジェクト指向を書いてみよう