akimachoのはてなブログ

ICTとデザインのためのブログ

Perlのクロージャについてメモ

はじめに

クロージャをよく知らなかったのでメモ。

クロージャとは

クロージャについて書かれていた本から説明を借ります。

Perlの「クロージャ」(closure)は、スコープの外に出た後のレキシカル変数を参照するサブルーチンである。それでも消えない理由は、そのサブルーチンからのリファレンスが、まだ存在するからだ。クロージャは、データを名前付きサブルーチンまたは新しい無名サブルーチンに「閉じ込める」用途に使用できる。

以上はJoseph N.Hall他『Effective Perl第2版』(翔泳社)pp.168-72からの引用。

宣言時に存在していたすべてのレキシカル変数にアクセスできるようなサブルーチンをクロージャ(または閉包。元は数学用語です)と呼びます。Perlではクロージャとはスコープから外れたレキシカル変数を参照するサブルーチンのことです。

以上はRandal L.Schwartz他『続・初めてのPerl 改訂第2版』(オライリー・ジャパン)p114からの引用。

まとめると、スコープを抜けたあとでも変数が生きていて、なおかつその変数にアクセスできるという感じですかね。

余談ですが、西尾泰和『コーディングを支える技術』では、クロージャは変数と関数をまとめるための方法として登場します。「変数と関数をまとめる」とは、言ってみればオブジェクト指向プログラミングにおけるオブジェクトを作るということです。逆に考えると、クロージャでできることはオブジェクトでもできるというわけですかね。

コードによる例

以下に示すのは、クロージャを使ったカウンタプログラムです。

ちなみにこのコードは、『コーディングを支える技術』ではJavaScriptで書かれているコードをPerlで書いてみたものです(若干違います)。

use strict;
use warnings;

sub makeCounter {
    my $count = 0;
    sub { $count++ };
}

my $c = makeCounter();
my $d = makeCounter();

print $c->(), "\n";#=> 0
print $d->(), "\n";#=> 0
print $d->(), "\n";#=> 1
print $c->(), "\n";#=> 1
print $c->(), "\n";#=> 2

まず、makeCounter()のスコープ内でレキシカル変数$countと値0の対応表ができます。

対応表

名前
$count 0

makeCounter()の戻り値として返される無名サブルーチンは、$countを参照しているので上の対応表を掴んだままmakeCounterの外に抜けます。

返された無名サブルーチンはmakeCounter()内のスコープで作った対応表を持っているので、スコープを抜けたあとでも$countを参照が可能です。

上の説明は、西尾泰和『コーディングを支える技術』(技術評論社)pp.206-207の説明をなぞらえて書いています。

あと、$cと$dに格納されたサブルーチンリファレンス(コードリファレンス)を呼び出しても、お互いにカウントには影響を及ぼしていないこと、つまりお互いのクロージャは独立していることが分かりますね。

state変数

Perl5.10からstate変数というものが使えるようです。このstate変数を利用すれば上に似たプログラムを書くことができます。

以下に示すのは、state変数を使ったカウンタプログラムです。

use strict;
use warnings;
use 5.010;
sub counter {
  state $count = 0;
  $count++;
}
print counter(), "\n";#=> 0
print counter(), "\n";#=> 1
print counter(), "\n";#=> 2

最初にサブルーチンcounter()が呼び出された時に$countに0が代入され、それ以降はstate $count = 0の行は無視されるようになります。

しかし、新たにカウンタが欲しい場合は別のカウンタサブルーチンを定義しなければいけなくなります。その点、クロージャを使った場合のほうが楽だと思います。

おわりに

まだもやもやしているので、他の本もあたってみたいと思います。

続・初めてのPerl 改訂第2版

続・初めてのPerl 改訂第2版

Effective Perl 第2版

Effective Perl 第2版