Iterator,Closure v.s. OOP
イテレータはオブジェクトインターフェースの一つで、nextメソッド等でオブジェクトが保持する次の要素を必要な時に取得することができるものです。リスト自体が巨大であったり、リストの生成に時間がかかる場合や、必要となるリストや要素についての情報がわからない場合に、必要量だけ取得できるのがイテレータの利点です。
最小値と最大値を指定されたリストを生成し、イテレータで順にその値を取得するオブジェクトの実装について考えてみます。
my $it = upto(7, 11); $n = NEXTVAL( $it ); # $n == 7 push @a, NEXTVAL( $it ) for 1..3; # @a == (8,9,10) $x = NEXTVAL( $it ); # $x == 11 # この時点で$it == undefined unless (defined NEXTVAL( $it ) ) { print "The iteretor is exhausted.\n"; }
OOPで実装する場合、以下のようになります。
package Upto; use Exporter; @ISA = qw( Exporter ); @EXPORT = qw( upto NEXTVAL ); sub upto { Upto->new( @_ ) } sub new{ my ($class, $lo, $hi) = @_; my $self = { LO => $lo, HI => $hi }; bless $self => $class; } sub NEXTVAL { my $self = shift; my ($lo, $hi) = @{$self}{'LO', 'HI'}; return if $lo > $hi; return $self->{LO}++; }
Closure版の実装は以下のようになります。
sub upto { my ($lo, $hi) = @_; return sub { return if $lo > $hi; return $lo++; }; }
これだけです。OOP版に比べて短く、新しいクラスのためのファイルを用意する必要もありません。
Closure版では無名関数を返すため、以下のような使い方となります。
my $it = upto( 3, 4 ); $x = $it->(); # $x == 3 $y = $it->(); # $y == 4 $z = $it->(); # $z == undefined
無名関数の->()によるイテレータ呼び出しはタイプ量が少ないし、関数コールであることが明示的です。最初に決めたNEXTVAL関数でイテレータ呼び出しするためには、次のような関数を用意します。
sub NEXTVAL { my $it = shift; $it->(); }
Closureによる軽量な実装は好きだけど->()インターフェースは気に入らない、という方のために、ClosureとOOPを合わせてみます。
sub upto { my ($lo, $hi) = @_; return bless sub { return if $lo > $hi; return $lo++; } => 'Upto'; } @Upto::ISA = ('Iteretor'); sub Iteretor::nextval { my $self = shift; $self->(); }
uptoの戻り値は、通常のオブジェクトと変わらず、$iterator->nextvalで値を取得できます。
イテレータはイテレータ呼び出しされる間の状態を保持します。そのため、次回呼び出し時に前回の続きから再開できます。OOPによる実装ではメンバデータとして状態を保持し、クロージャ版ではクロージャに納められたレキシカル変数に状態を保持します。クロージャとOOPを合わせたバージョンでは、クロージャ形式で状態を保持し、クロージャをblessします。どの実装でもイテレータ呼び出しで状態を更新し、現在の状態を返します。
……というようなことがHigher-Order Perlに書いてありました。p.26 - p.33辺りまで。イテレータでFile::Find代替モジュール生成の話は、気が向いたらまた続きやります。
Higher-Order Perl: Transforming Programs with Programs
- 作者: Mark Jason Dominus
- 出版社/メーカー: Morgan Kaufmann
- 発売日: 2005/03/14
- メディア: ペーパーバック
- クリック: 21回
- この商品を含むブログ (30件) を見る