第56回 クロージャの実践的使いどころ
あまりPerlでクロージャを使っている例をみたことがなくて(僕の勉強不足もあるだろうけど)、
友人に聞いてみたところ、
コールバックかな。
と言われたので、やってみよう。
コールバック
そもそもコールバックとはなにかというと、
なにかの関数に関数を与えると、その与えた関数を呼んでくれる仕組みである。
言葉で説明すると、わかりにくいのでソースを提示する。
use strict; sub call_you { my $code = shift; print '今から君の関数を呼ぶ', "\n"; $code->(); # コールバック; print '呼び終えました', "\n"; } my $f = sub { print '!!呼んで!!', "\n"; }; call_you($f);
結果:
今から君の関数を呼ぶ !!呼んで!! 呼び終えました
コールバックを使った関数
では、次のような関数を考えてみる。
第1引数に関数を受け取る。
第2引数に数字を受け取る。
第1引数に受け取った関数を、第2引数ぶんだけ実行する。
第1引数に受け取った関数には、今何回目のループか伝える。
これを実装したのは以下の通り。
sub hoge{ my ($code, $count) = @_; for(1..$count){ $code->($_); } }
これをつかって、つぎのようなソースを書いてみよう。
use strict; sub hoge{ my ($code, $count) = @_; for(1..$count){ $code->($_); } } my $f = sub { my $i = shift; my $str = '*'; $str = $str x $i; print $str, "\n"; }; hoge($f, 2);
結果:
* **
もっと汎用的なものを与えたい
いま与えた関数では、'*'しか表示できないので、
$fには何か引数を与えて、色んな文字を表示できるようにしたい。
だが、hoge関数をいじることはもうできないという条件を加えたとすると(誰かが作った関数だとかそういう状況)、とても悩ましいことになる。
use strict; sub hoge{ my ($code, $count) = @_; for(1..$count){ $code->($_); } } my $f = sub { my ($str, $i) = @_; #文字を受け取るように改修 $str = $str x $i; print $str, "\n"; }; hoge($f->('!'), 2);
このようにやってみても、hoge( )が実行されるまえに、
$f->('!')の部分が実行されてしまうからである。これでは、hogeに関数を与えられない。
大域変数
そこで、大域変数を使って次のようにすれば解決するのではないかと考える。
use strict; my $str = ''; # 大域変数 sub hoge{ my ($code, $count) = @_; for(1..$count){ $code->($_); } } my $f = sub { my $i = shift; # ここからは大域変数で処理 $str = $str x $i; print $str, "\n"; }; for('*', '!', '?'){ $str = $_; # 大域変数に代入 hoge($f, 2); print '------', "\n"; }
結果:
* ** ------ ! !! ------ ? ?? ------
たしかにうまくいくのだが、大域変数を使うというのはなんとも喜ばしくない。
大域変数は避けたい気持ちでいっぱいになる。
そこでクロージャ
クロージャを使って、大域変数を使う魔の誘惑から逃れる。
use strict; sub hoge{ my ($code, $count) = @_; for(1..$count){ $code->($_); } } my $f = sub { my $str = shift; sub { my $i = shift; $str = $str x $i; print $str, "\n"; }; }; for('*','!','?'){ hoge($f->($_), 2); # $f->($_) は毎回クロージャを生成 print "---- \n"; }
結果:
* ** ---- ! !! ---- ? ?? ----
これは、クロージャの「自分が作られたときの状態を保持する」という性質をうまく使っている。
さらに、hogeには関数しか与えれないという条件をうまく乗り越えることができている。自分が作られたときの状態を保持しているのは何を隠そう関数なのだから(正確に言うと関数へのリファレンス)。