第58回 クロージャをもっとていねいに

では、クロージャをどのように作るのか説明してみる。
基礎になっている知識から説明していき、自然にクロージャが作れるようにするのを狙う。
最後に、クロージャで定数を作ってみる。

関数に関数を返してもらう

次のソースをみてもらおう。

use strict;

sub return_sub {
  sub { print 'こんにちは', "\n" };
}

my $c = return_sub();
my $d = return_sub();
my $e = return_sub();

$c->();
$d->();
$e->();

結果:

こんにちは
こんにちは
こんにちは

注目は、return_sub関数である。この関数は、

sub { print 'こんにちは', "\n" }

という無名関数をただ返しているだけである。
return_sub関数を呼び出すと、関数が生み出されると考えると良い。
これはFactory Method パターンのようなものである。
return_subは、関数を生み出す工場なのである。

return_subに引数を与える

では、つぎのように return_subが引数を受け取れるようにしてみよう。

use strict;

sub return_sub {
  my $str = shift;
  sub { print $str, "\n" };
}

my $c = return_sub('おはよう');
my $d = return_sub('こんにちは');
my $e = return_sub('こんばんは');

$c->();
$d->();
$e->();

結果:

おはよう
こんにちは
こんばんは

無名関数がもっている$strは、return_sub関数の$strである。
なんと、関数の中から、外側の関数の変数が見れるのである。
これが、「自分が作られたときの状態を保持する」ということである。

$c->();
$d->();
$e->();

のところは、みんな同じように引数をあたえずただ無名関数を発動しているだけである。
なのに出力された値はバラバラである。

なぜこんなことができるようになったかというと、
return_sub関数が引数を受け取り、バラエティー豊かな関数を生み出せるようになったからである。
まさに工場なのである。

別の工場をつくってみよう

se strict;

sub other_return_sub {
  my $num = shift;
  sub { print $num, "\n";
        $num++;
      };
}

my $c = other_return_sub(1);
my $d = other_return_sub(5);
my $e = other_return_sub(12);

# それぞれ1回目の発動
$c->();
$d->();
$e->();

# それぞれ2回目の発動
$c->();
$d->();
$e->();

結果:

1
5
12
2
6
13

今回返す無名関数は、

  sub { print $num, "\n";
        $num++;
      };

という形をしている。
$numを出力した後に、$num++ をしているのだ。
だから、

# それぞれ2回目の発動
$c->();
$d->();
$e->();

の出力結果が、

2
6
13

と1回目に発動したときよりも値が増えているのである。
これが、「保持した環境に影響をあたえることができる」ということである。

クロージャで定数をつくる

Perlでは定数を使うにはモジュールを使ったりとかしたりしないといけないので、
自前で作ってみる方法を考える。定数というのは、値をずっと変わらずに保持していてくれればいいので、
「自分が作られたときの状態を保持する」という性質とうまくマッチする。

use strict;

sub make_const {
  my $c = shift;
  sub { $c->{$_[0]} };
}

my $const = make_const( {'H' => 'Hello',
                         'B' => 'Bay'
                        });

print $const->('H'), "\n";
$const->('H') = 'Hoge';     # 文法エラー:関数呼び出しに代入はできない

結果:

Hello
Can't modify non-lvalue subroutine call at e line 13.

定数を作ると言っておきながら、作るのは関数になる。
その作られた関数に定数のように振る舞ってもらうのである。


このように、

sub make_const {
  my $c = shift;
  sub { $c->{$_[0]} };
}

とすることで、
$cには、無名関数でしかアクセスできないようにしてしまうのである。
このようにして、$cを変更する余地をなくすようにして定数のようなもの(値が変わらないもの)を実現している。