クロージャー
お仕事でクロージャーというコーディングの仕方を覚えたのでメモメモ。
Perlに限らず利用出来る言語は多い(どうやらPHPもあるらしい、、知らなかった)
どんなプログラム言語だって変数を使う場合はスコープ(変数の利用可能な範囲)が決まっています。
下記はスコープの例
sub test { my $a = 2; print $a . "\n"; } my $a = 1; print $a . "\n"; test(); print $a . "\n"; ▼実行結果 1 2 1
メインルーチンで利用している$aとsubで利用している$aは別物になります。
この変数のスコープ(変数の利用可能な範囲)を正しく理解していルコトが、
クロージャーを理解する上で非常に重要になってきます!
※今度スコープについてもまとめておこう。。僕も危ういときがあります。。
話は少し逸れますが、Perlは関数(sub)をreturnする事が出来る言語です。
どういう事かというと
sub counter { my ($num,) = @_; $num++; return $num; } my $a = 0; $a = counter($a); print $a . "\n"; $a = counter($a); print $a . "\n"; ▼実行結果 1 2
というコードをこのように書けます。
sub counter { return sub { my ($num,) = @_; $num++; return $num; }; } my $a = 0; $obj = counter(); $a = $obj->($a); print $a . "\n"; $a = $obj->($a); print $a . "\n"; ▼実行結果 1 2
凄いですよね〜
では、本題に戻ります。
下記の実行結果はどうなるでしょう。
sub counter { my $count = 0; return sub { my ($num,) = @_; $count += $num; return $count; }; } $obj = counter(); print $obj->(5) . "\n";
$objには関数が格納されていて、引数を渡して実行できます。
しかし、関数内で利用されている$countという変数はメインルーチンでは定義されていないですよね?
でもエラーにはなりません。
0に5が加算されて「5」と表示されます。
$countが定義されたのはメインルーチンではなく、sub counterのスコープ内だからです。
というわけで、更に3を加算するとこんな感じの動作になる。
sub counter { my $count = 0; return sub { my ($num,) = @_; $count += $num; return $count; }; } $obj = counter(); print $obj->(5) . "\n"; print $obj->(3) . "\n"; ▼実行結果 5 8
念のため書くと$countをmy宣言する場所を下記のようにすると
毎回変数が初期化されちゃいますよね。これだと普通のsubです。
sub counter { return sub { my $count = 0; my ($num,) = @_; $count += $num; return $count; }; } $obj = counter(); print $obj->(5) . "\n"; print $obj->(3) . "\n"; ▼実行結果 5 3
ちなみにこういう事ならグローバル変数でも実現は可能です。
明確な違いはクロージャーであれば「変数名を占有する事無く実現出来る」というところでしょうか。
こんな処理を作る事も出来ます。
sub counter { my $num; return { plus => sub { $num += $_[0]; return $num; }, minus => sub { $num -= $_[0]; return $num; } } } my $obj = counter(); print $obj->{plus}(5) . "\n"; print $obj->{plus}(3) . "\n"; print $obj->{minus}(7) . "\n"; ▼実行結果 5 8 1
通常のsubが行うのは「値を渡すと、様々な処理を施し、結果を返す」ですが、
クロージャーが可能にするのは「渡す値によって機能の異なる処理を作れる」なのです!
あんまり良い例が書けませんでしたが、、
クロージャーって面白いですねぇ。