ROUTE 3390

備忘録的な用途で書いていますが、どなたかの役に立つ事があれば嬉しいです。

クロージャー

お仕事でクロージャーというコーディングの仕方を覚えたのでメモメモ。

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が行うのは「値を渡すと、様々な処理を施し、結果を返す」ですが、
クロージャーが可能にするのは「渡す値によって機能の異なる処理を作れる」なのです!

あんまり良い例が書けませんでしたが、、

クロージャーって面白いですねぇ。