第129回 クロージャは作られたときの状態を保持するわけではなく参照する

第55回 クロージャ - bingo_nakanishiの他言語出身者のためのPerl入門
第56回 クロージャの実践的使いどころ - bingo_nakanishiの他言語出身者のためのPerl入門
第58回 クロージャをもっとていねいに - bingo_nakanishiの他言語出身者のためのPerl入門

私は今まで、「クロージャは作られたときの状態を保持する」という言い方をしてきたが、
これは厳密には間違いだったようだ。

状態を保持しているように見える例

use strict;

sub h{
  my $c = shift;
  sub { $c++ };
};

my $h = h(5);
print $h->(), "\n";
print $h->(), "\n";
print $h->(), "\n";


結果:

5
6
7

この例だと、確かに作られたときの状態を持っており、その状態に変化を加えているように見える。


厳密に言うと「作られたときの変数を参照している」

しかし、次の例を見てみると、

use strict;

sub h{
  my $c   = shift;
  my $fun = sub { $c++ };  # ここで作った

  $c = 10;                 # $cを変更

  return $fun;             # 作った関数を返すのはココ
}

my $h = h(5);
print $h->(), "\n";
print $h->(), "\n";
print $h->(), "\n";


結果:

10
11
12

このように、$cは作られたときではなく、$c = 10;が実行されたものを見ている。
つまりこれは、関数が作れたときの状態を保持するのではなく、作られたところの外側の変数をただ参照していることを示している。



同じ変数をクロージャとして持つ関数を2つ返してみる

use strict;

sub h{
  my $c   = shift;
  [
   sub{ $c++; print $c, "\n" },
   sub{ $c++; print $c, "\n" }
  ];
}

my($fun1, $fun2) = @{h(5)};

$fun1->();
$fun1->();
$fun2->();
$fun1->();


結果:

6
7
8
9

このようにお互いに同じ変数をクロージャとして持つ場合は、
参照であるので、お互いの関数は同じ$cを更新している。


Rubyでも念のために見てみる

class Hoge
  def foo
    c = 1
    [1,2,3].each do |i|
                     c=c+1    # cを更新
                     puts c
                 end
    puts
    puts c                    # cが更新されているのがわかる
  end
end

Hoge.new.foo


結果:

2
3
4

4

確かに外側のcが更新されている。


ただクロージャをよく使う場面を考えると

クロージャをよく使う場面を考えると、
「参照」という言葉よりも、
「作られたときの状態を保持する」
という表現の方がしっくりくる気がする。