Perl Hackers Hub

第20回リファレンス入門(3)

(1)こちら⁠2)こちらから。

Data::Dumperモジュール

リファレンスを使って複雑なデータ構造を作れるようになりましたが、このようなデータを表示するには、どうすればよいでしょうか?すでに説明したように、リファレンスを文字列として扱うとHASH(0x116360)のような文字列に変換されます。ですから、printの引数としてリファレンスを渡すと、このような文字列が表示されてしまいます。これでは役に立ちませんね。

Perlは、リファレンスを含むような複雑なデータ構造をわかりやすく表示するData::Dumperモジュールを備えています[6]⁠。このモジュールはDumper関数をエクスポートします。Dumper関数は、渡されたデータ構造を人間が読める文字列に変換してくれます。

たとえば先ほど値を設定した$userinfoの内容を表示するには、次のようにします。

use Data::Dumper;
print Dumper($userinfo);

このコードの実行結果をリスト3に示します。

リスト3 Data::DumperモジュールのDumper関数を使って、複雑なデータ構造を表示する
1:$VAR1 = {
2:         'uid' => 1001,
3:         'env' => {
4:                    'HOME' => '/home/cond',
5:                    'TERM' => 'VT100'
6:                     },
7:         'name' => 'cond',
8:         'gid' => [
9:                    1,
10:                   200,
11:                   201,
12:                   203
13:                 ]
14:       };

無名サブルーチン

Perlは、名前を持たないサブルーチン..無名サブルーチンanonymous subroutineと呼びます..をサポートしています[7]⁠。無名サブルーチンは次のように書きます。

sub { ..... }

まず先頭にキーワードsubを置き、その後ろにブレースで囲んだ文の並びを置きます。これは、普通のサブルーチン定義から、サブルーチン名を取り除いたものになっています。Perlは無名サブルーチンがあると、名前を持たないサブルーチンを定義して、それへのリファレンスを返してくれます。

次のコードでは、無名サブルーチンが生成されて、それへのリファレンスが変数$frefに代入されます。

$fref = sub { print "Hello, $_[0]\n"; };

この無名サブルーチンは、第1引数で指定した名前に対して、"Hello, XXXX"と呼びかけるメッセージを表示するものです。次のように書けば、"world"という引数を渡してこの無名サブルーチンを呼び出すので、"Hello, world"と表示されます。

&$fref("world");

また、配列リファレンスやハッシュリファレンスと同様に、->を使った書き方もできます(こちらの書き方のほうが好まれます⁠⁠。

$fref->("world");

クロージャ

無名サブルーチンを生成する際には、その無名サブルーチンで使われるレキシカル変数(myで宣言された変数)の実体も、同時に封じ込められます。こうして生成された無名サブルーチンのことをクロージャclosureと呼びます。これだけの説明では何のことやらよくわからないと思いますので、例をもとに説明しましょう。

クロージャを返す関数greeting

リスト4では、greetingという関数(サブルーチン)を定義しています。この関数は引数を1個受け取って、それをmy変数$nameに代入します(2行目⁠⁠。次に、4行目のreturn文で無名サブルーチンへのリファレンスを返します。返される無名サブルーチンは4~6行目で定義されていますが、実質的な処理は5行目のprintでメッセージを表示するだけです。

リスト4 関数greeting:あいさつをする関数を返す
1:sub greeting {
2:    my ($name) = @_; # この変数が封じ込められる
3:
4:    return sub {
5:        print "Hello, $name.\n";
6:    };
7:}
8:
9:my $john = greeting("John");
10:my $tom = greeting("Tom");
11:
12:$john->(); # Hello, John.
13:$tom->(); # Hello, Tom.

5行目のprintの引数の文字列に注目してください。この文字列では、変数$nameが変数展開されています。この$nameという変数は、2行目で宣言されているレキシカル変数(my変数)です。本来は、変数$nameは、関数greetingが実行されている間だけ存在し、関数から抜けた時点で消滅します。

しかし、5行目の文字列の中で変数$nameを使っているのでクロージャが生成されて、その中に変数$nameの実体が封じ込められます。関数greetingの実行が終了するとmy変数$nameは消滅しますが、無名サブルーチン(クロージャ)が存在する間、変数$nameの実体はそのクロージャの中に存在し続けることになります。

9行目では文字列"John"を渡して、関数greetingを呼び出しています。関数greetingは、変数$nameに"John"が代入された状態でクロージャを生成して返します。返されたクロージャは変数$johnに代入します。

同様に、10 行目では文字列"Tom"を渡して関数greeting を呼び出しています。今度は、変数$nameに"Tom"が代入された状態でクロージャが生成されます。返されたクロージャを変数$tomに代入しています。

関数greetingは、呼び出されるたびに新たにクロージャを生成して返す、ということに注意してください。そのため、9行目で生成されるクロージャと、10行目で生成されるクロージャとは、まったく別ものとなります。

12行目では、$johnに入っているクロージャを呼び出します。その結果、⁠Hello, John.」と表示されます。また13行目では$tomに入っているクロージャを呼び出します。今度は「Hello, Tom.」と表示されます。

クロージャに引数を渡す

クロージャに対して引数を渡すこともできます。リスト5では、adderという関数を定義しています。関数adderは、指定した数を加算して返す関数(クロージャ)を返します。たとえば、adder(1)として呼び出すと、⁠引数に1を加算する関数(クロージャ⁠⁠」を返します。

リスト5 関数adder:指定した数を加算して返す関数を返す
1:sub adder {
2:    my ($increment) = @_; # この変数が封じ込められる
3:
4:    return sub {
5:        my ($x) = @_;
6:        return $x + $increment;
7:    }
8:}
9:
10:my $add1 = adder(1); # 1 を加算する関数
11:my $add20 = adder(20); # 20 を加算する関数
12:
13:print $add1->(10), "\n"; # 11
14:print $add20->(10), "\n"; # 30
15:print $add1->(30), "\n"; # 31

関数adderはリスト4の関数greetingと似ていますが、返されるクロージャが引数を受け取る、という点が違います。4~7行目で無名サブルーチン(クロージャ)を生成して返しています。5行目では、受け取った引数を変数$xに代入しています。そして6行目では、$xと$incrementを加算して、その結果を返します。変数$incrementは、関数adderに渡された値が代入された状態で、クロージャに封じ込められています。

結局、関数adderが返すクロージャは、受け取った引数に$incrementの値を加えた値を返すことになります。

リスト5では、10行目では「1を加算する関数⁠⁠、11行目では「20を加算する関数」を生成して、それぞれ変数$add1と$add20に代入しています。13~15 行目では、これらの関数に引数を渡して呼び出しています(各行では、コメントに書いてある値が表示されます⁠⁠。

複数のクロージャで変数を共用する

これまでの例ではクロージャを1個生成して返していましたが、複数のクロージャを生成して返すこともできます。その場合には、生成された複数のクロージャは変数を共用することになります。

リスト6の関数make_counterは、2つのクロージャを生成して、それを2要素のハッシュとして返します。4行目のreturnの後ろの「{」と7行目の「}」は、先ほど説明した無名ハッシュを表す記法です。5行目と6行目は、この無名ハッシュの要素になります。5行目は、

inc => sub { return ++$counter },

となっていますが、incがキーになり、⁠sub { return++$counter }」で生成されたクロージャが対応する値になります。このクロージャを呼び出すと、変数$counterの値を1つ増やして、⁠増やしたあとの)$counterの値を返します。同様に6行目では、decがキーで、変数$counterの値を減らすクロージャが対応する値となります。

リスト6 関数make_counter:カウンタ関数を返す
1:sub make_counter {
2:    my ($counter) = @_; # この変数が封じ込められる
3:
4:    return {
5:        inc => sub { return ++$counter },
6:        dec => sub { return --$counter }
7:    };
8:}
9:
10:my $foo = make_counter(0); # 初期値0 のカウンタ
11:my $bar = make_counter(100); # 初期値100 のカウンタ
12:
13:print $foo->{inc}->(), "\n"; # 1
14:print $bar->{inc}->(), "\n"; # 101
15:print $bar->{inc}->(), "\n"; # 102
16:print $foo->{inc}->(), "\n"; # 2
17:print $bar->{inc}->(), "\n"; # 103
18:print $foo->{inc}->(), "\n"; # 3
19:print $foo->{dec}->(), "\n"; # 2
20:print $bar->{dec}->(), "\n"; # 102

5行目と6行目の無名サブルーチンは両方とも変数$counterを使っていますが、生成されるクロージャに含まれる変数$counterの実体は同一のものです。

10行目と11行目で、それぞれ初期値が0と100のカウンタを生成して、変数$fooと$barに代入しています。13行目以降では、カウンタの値を増減させて、動作を確認しています。カウンタの値を増やすには、ハッシュのキーincに対応するクロージャを呼び出します。たとえば$fooのカウンタを増加させるには、

$foo->{inc}->()

とします。減少させるには次のようにします。

$foo->{dec}->()

13~20行目では、2つのカウンタの値を適当に増減させています。各行では、コメントに書いてある値が表示されます。表示される値からは、変数$fooと$barに入っているカウンタが独立して動作していること、ハッシュに入っている2個のクロージャ(それぞれキーincとdecの値になっています)は同じ$counterの実体を共用していること、がわかります。

まとめ

今回はリファレンス全般についてお話ししました。リファレンスを使うと、複雑なデータ構造が表現できるようになります。また、リファレンスを利用して、多次元配列や多次元ハッシュをエミュレートできます。無名配列、無名ハッシュ、無名サブルーチンの書き方も説明しました。これらの記法を使うと、名前を持たない配列やハッシュやサブルーチンが生成されて、それへのリファレンスが得られます。また、無名サブルーチンの特別なケースであるクロージャの動作について、具体例を挙げて説明しました。

リファレンスやクロージャを使うことにより、Perlプログラミングの世界が大きく広がります。この機会にぜひマスターしてください。

さて、次回の執筆者は中川勝樹(@ikasam_a)さんで、テーマは「Carton & cpanm」です。

おすすめ記事

記事・ニュース一覧