Hatena::ブログ(Diary)

bingo_nakanishiの他言語出身者のためのPerl入門

2009-07-11

第134回 モンキーパッチングをPerlでやるとこうだろうか

01:54 | 第134回 モンキーパッチングをPerlでやるとこうだろうかを含むブックマーク

まつもとゆきひろ コードの世界?スーパー・プログラマになる14の思考法

まつもとゆきひろ コードの世界?スーパー・プログラマになる14の思考法

をすごく楽しく読んでいるとモンキーパッチングという発想がでてきました。


簡単に言うと、クラスを後から(実行時に)書き換えて、能力を追加することである。

起源は他人のバグを後付けで回避するところから始まったとのことです。


Ruby

class Bar
  def initialize(n)
    @name = n
  end
  def say1
    puts @name + 'です'
  end
end

bar = Bar.new('バー');
bar.say1

class Bar
  def say2
    puts @name + 'やがな'
  end
end

bar.say2

結果:

バーです
バーやがな


Perl

Perlにはクラスというのは存在せず、ただパッケージに属した関数を呼んでいるだけなので、

この書き方はできません。

use strict;
package Bar;
sub new {
  my $thing = shift;
  my $class = ref $thing || $thing;
  bless { @_ }, $class;
}

sub say1 {
  my $self = shift;
  print $self->{'name'} . 'です' ."\n";
}

package main;
my $bar = Bar->new( 'name' => 'バー' );
$bar->say1();

sub say2 {
  my $o = shift;
  print $o->{'name'} . 'やがな' . "\n";
}

say2($bar);

結果:

バーです
バーやがな

このようにsay2関数を作って、オブジェクト指向の書き方を崩すしか方法はないのでしょうか?


第1引数は暗黙的に補われる書式を思い出してみる

$o->say();

という書き方をした場合、$oがsayメソッドの第1引数に渡る事を思い出してみると、

use strict;
package Bar;
sub new {
  my $thing = shift;
  my $class = ref $thing || $thing;
  bless { @_ }, $class;
}

sub say1 {
  my $self = shift;
  print $self->{'name'} . 'です' ."\n";
}

package main;
my $bar = Bar->new( 'name' => 'バー' );
$bar->say1();

sub say2 {
  my $o = shift;
  print $o->{'name'} . 'やがな' . "\n";
}

$bar->main::say2();   # ここをオブジェクト指向っぽく書けた

結果:

バーです
バーやがな

このようにオブジェクト指向の書き方で書けました。

結局のところ、やはりインスタンス変数にあたるモノが属しているパッケージの関数を呼ぶわけではなく、強引にmainパッケージの関数を呼び出す書き方をしているだけですので、ここまでする必要があるかはいささか怪しいところがありますが、このようにも書けますということでした。



そもそもRubyはクラスを編集している

そもそもRubyはクラスを編集できているので、あたらしくそのクラスを元にnewしたものは追加されたメソッドが呼べます。

class Bar
  def initialize(n)
    @name = n
  end
  def say1
    puts @name + 'です'
  end
end

bar = Bar.new('バー')
bar.say1

class Bar
  def say2
    puts @name + 'やがな'
  end
end

bar.say2


other_bar = Bar.new('他のバー')
other_bar.say1()
other_bar.say2()

結果:

バーです
バーやがな
他のバーです
他のバーやがな

オープンクラスすごしです。



Perlはあくまでもmainのsay2を呼んだだけですので、

$other_bar = Bar->new( 'name' => '他のバー' );

としたところで、

$other_bar->main::say2();

としなければなりません....




※もっと良い方法がある。その方法だと、こんな不具合がでる。ということをご存知の方がいらっしゃいましたら教えていただけるとうれしいです。

lestrratlestrrat 2009/07/12 10:58 こういう感じかな。http://gist.github.com/145481

lestrratlestrrat 2009/07/12 11:00 Mooseで書くならこう。http://gist.github.com/145482

lestrratlestrrat 2009/07/12 11:09 特定のインスタンスに対するモンキーパッチングならこっち http://gist.github.com/145485

lestrratlestrrat 2009/07/12 11:19 そういえばもっと単純な方法もありました:http://gist.github.com/145489

bingo_nakanishi_perlbingo_nakanishi_perl 2009/07/12 22:52 ありがとうございます。

わあ、Mooseってこういうことができるんですね。勉強になります。

おお、グロブで代入する方法があるんですね。なるほどこういう風に使えるんですね。ありがとうございます。

bingo_nakanishi_perlbingo_nakanishi_perl 2009/07/12 22:55 たしかに、グロブを使った場合は、新しくnewしたものもメソッドが呼び出せる事を確認できました。