5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Perlのモジュール側のクロージャで、値を保持し続けてほしかった

Last updated at Posted at 2014-06-12

何がしたかったか

useしているモジュール側にクロージャがある場合、何度呼び出しても値を保持しておいて欲しいなーって思いました。

元のコード

closer.pl
use strict;
use warnings;
use CloserModule;

my $closer_module = CloserModule->new();
print $closer_module->get_count(), ', ';
print $closer_module->get_count();

exit;
CloserModule
package CloserModule;
use strict;
use warnings;

sub new {
	my $pkg = shift;
	bless {}, $pkg;
}
sub get_count{
	my $self = shift;
	my $closer = sub {
		my $count = 0;
		return sub {
			$count++;
			return $count;
		};
	}->();
	return $closer->();
}
1;
結果
1, 1

closer.pl側でget_countを呼び出すたびに$closerという変数が初期化されているので、毎回$countが0になってしまいます。
できれば$countを保持し続けてほしいな、と思ったのでした。

修正したコード

偉大な先人たちのモジュールを見ていると、変数がサブルーチンの外で宣言・初期化されていることがよくあったので、真似してみました。

closer.pl
use strict;
use warnings;
use CloserModule;

my $closer_module = CloserModule->new();
print $closer_module->get_count(), ', ';
print $closer_module->get_count();

exit;
CloserModule
package CloserModule;
use strict;
use warnings;

sub new {
	my $pkg = shift;
	bless {}, $pkg;
}
my $closer = sub {
	my $count = 0;
	return sub {
		$count++;
		return $count;
	};
}->();
sub get_count{
	my $self = shift;
	return $closer->();
}
1;
結果
1, 2

変数$closerをサブルーチンの外で宣言・初期化しているので、何度get_countを呼んでも$countの値は保持されたまま($countが再度宣言・初期化されないまま)になります。

ついでに

closer.pl
use strict;
use warnings;
use CloserModule;

my $closer_module = CloserModule->new();
print $closer_module->get_count(), ', ';
print $closer_module->get_count(), ', ';

my $closer_module2 = CloserModule->new();
print $closer_module2->get_count(), ', ';
print $closer_module2->get_count();

exit;

とした場合。

結果
1, 2, 3, 4

となりました。
つまり、サブルーチン外の変数はnewした際に初期化されるのではなくて、useした際に初期化されて、モジュール側に値が残り続けるということなのですね。
当然といえば当然ですが。。。

newした際には$countを0からスタートさせたい

CloserModule
package CloserModule;
use strict;
use warnings;

my $closer;

sub new {
	my $pkg = shift;
	$closer = sub {
		my $count = 0;
		return sub {
			$count++;
			return $count;
		};
	}->();
	bless {}, $pkg;
}
	
sub get_count{
	my $self = shift;
	return $closer->();
}
1;

のように、$closerはサブルーチンの外で宣言をして、newの中で中身を代入してあげれば、newした際に$countが(というか、$closer自体が)初期化されます。
これで、結果は下記の通りになりました。

結果
1, 2, 1, 2

しかし、

closer.pl
use strict;
use warnings;
use CloserModule;

my $closer_module = CloserModule->new();
print $closer_module->get_count(), ', ';
print $closer_module->get_count(), ', ';
print $closer_module->get_count(), ', ';

my $closer_module2 = CloserModule->new();
print $closer_module2->get_count(), ', ';
print $closer_module2->get_count(), ', ';

print $closer_module->get_count();	#(1)

exit;

とした場合に、(1)の部分では4が返るのを期待していましたが、実際には、、、

結果
1, 2, 3, 1, 2, 3

となりました。
サブルーチン外の変数は影響範囲が大きいですね。

前項の(1)で4が返るようにする

思いついたのは、おとなしくblessすれば? という事くらいしかありませんでした。

CloserModule.pm
package CloserModule;
use strict;
use warnings;
sub new {
	my $pkg = shift;
	bless {
		closer => sub {
			my $count = 0;
			return sub {
				$count++;
				return $count;
			};
		}->(),
	}, $pkg;
}
sub get_count{
	my $self = shift;
	
	#return $closer->();
	return $self->{closer}->();
}

1;

でもこれだったら普通に

CloserModule.pm
package CloserModule;
use strict;
use warnings;
sub new {
	my $pkg = shift;
	bless {
		count => 0,
	}, $pkg;
}
sub get_count{
	my $self = shift;
	
	return ++$self->{count};
}

1;

で良かったりします。

なんか他に方法ないのかなー?
もちろん、blessでもいいんですけどね。

何はともあれ、想定とは違う部分でいろいろ勉強になりました。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?