(1)はこちら、(2)はこちらから。
Data::Dumperモジュール
リファレンスを使って複雑なデータ構造を作れるようになりましたが、このようなデータを表示するには、どうすればよいでしょうか?すでに説明したように、リファレンスを文字列として扱うとHASH(0x116360)のような文字列に変換されます。ですから、printの引数としてリファレンスを渡すと、このような文字列が表示されてしまいます。これでは役に立ちませんね。
Perlは、リファレンスを含むような複雑なデータ構造をわかりやすく表示するData::Dumperモジュールを備えています[6]。このモジュールはDumper関数をエクスポートします。Dumper関数は、渡されたデータ構造を人間が読める文字列に変換してくれます。
たとえば先ほど値を設定した$userinfoの内容を表示するには、次のようにします。
このコードの実行結果をリスト3に示します。
無名サブルーチン
Perlは、名前を持たないサブルーチン..無名サブルーチン(anonymous subroutine)と呼びます..をサポートしています[7]。無名サブルーチンは次のように書きます。
まず先頭にキーワードsubを置き、その後ろにブレースで囲んだ文の並びを置きます。これは、普通のサブルーチン定義から、サブルーチン名を取り除いたものになっています。Perlは無名サブルーチンがあると、名前を持たないサブルーチンを定義して、それへのリファレンスを返してくれます。
次のコードでは、無名サブルーチンが生成されて、それへのリファレンスが変数$frefに代入されます。
この無名サブルーチンは、第1引数で指定した名前に対して、"Hello, XXXX"と呼びかけるメッセージを表示するものです。次のように書けば、"world"という引数を渡してこの無名サブルーチンを呼び出すので、"Hello, world"と表示されます。
また、配列リファレンスやハッシュリファレンスと同様に、->を使った書き方もできます(こちらの書き方のほうが好まれます)。
クロージャ
無名サブルーチンを生成する際には、その無名サブルーチンで使われるレキシカル変数(myで宣言された変数)の実体も、同時に封じ込められます。こうして生成された無名サブルーチンのことをクロージャ(closure)と呼びます。これだけの説明では何のことやらよくわからないと思いますので、例をもとに説明しましょう。
クロージャを返す関数greeting
リスト4では、greetingという関数(サブルーチン)を定義しています。この関数は引数を1個受け取って、それをmy変数$nameに代入します(2行目)。次に、4行目のreturn文で無名サブルーチンへのリファレンスを返します。返される無名サブルーチンは4~6行目で定義されていますが、実質的な処理は5行目のprintでメッセージを表示するだけです。
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を加算する関数(クロージャ)」を返します。
関数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 }」で生成されたクロージャが対応する値になります。このクロージャを呼び出すと、変数$counterの値を1つ増やして、(増やしたあとの)$counterの値を返します。同様に6行目では、decがキーで、変数$counterの値を減らすクロージャが対応する値となります。
5行目と6行目の無名サブルーチンは両方とも変数$counterを使っていますが、生成されるクロージャに含まれる変数$counterの実体は同一のものです。
10行目と11行目で、それぞれ初期値が0と100のカウンタを生成して、変数$fooと$barに代入しています。13行目以降では、カウンタの値を増減させて、動作を確認しています。カウンタの値を増やすには、ハッシュのキーincに対応するクロージャを呼び出します。たとえば$fooのカウンタを増加させるには、
とします。減少させるには次のようにします。
13~20行目では、2つのカウンタの値を適当に増減させています。各行では、コメントに書いてある値が表示されます。表示される値からは、変数$fooと$barに入っているカウンタが独立して動作していること、ハッシュに入っている2個のクロージャ(それぞれキーincとdecの値になっています)は同じ$counterの実体を共用していること、がわかります。
まとめ
今回はリファレンス全般についてお話ししました。リファレンスを使うと、複雑なデータ構造が表現できるようになります。また、リファレンスを利用して、多次元配列や多次元ハッシュをエミュレートできます。無名配列、無名ハッシュ、無名サブルーチンの書き方も説明しました。これらの記法を使うと、名前を持たない配列やハッシュやサブルーチンが生成されて、それへのリファレンスが得られます。また、無名サブルーチンの特別なケースであるクロージャの動作について、具体例を挙げて説明しました。
リファレンスやクロージャを使うことにより、Perlプログラミングの世界が大きく広がります。この機会にぜひマスターしてください。
さて、次回の執筆者は中川勝樹(@ikasam_a)さんで、テーマは「Carton & cpanm」です。