undefを含むリスト代入とlocaltime

先日勉強したリスト代入と左辺値に関して、H.I.さんよりコメントを頂きました。ありがとうございます。

H.I. 『リスト代入の左辺リストが
「要素が全て有効な左辺値でなければならない」こと
の例外として、リスト代入の要素にはundefを指定することができます。
その場合、その部分の代入値は無視されます。』

なるほどなるほど! これは便利な Tips ですね。右辺のリストの不必要な要素は undef で捨てる事ができるわけですね。(^_^) というわけで、実際に使って確かめてみます。何事も経験といいます。


例えば、エポック秒世界標準時1970年1月1日午前0時0分0秒からの秒数)をローカルゾーンでの日時にして返す「localtime」という組み込み関数があります。この関数は、リストコンテキストで使うとローカル日時の各要素をリストで返してくれます。以下は「perldoc -f localtime」からの引用です。

   #  0    1    2     3     4    5     6     7     8
   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                                               localtime(time);

つまり、「秒」「分」「時」「日」「月」「年」「曜日」「1年365日の内何日目か」「Daylight Saving Time(サマータイム)期間中か」という9つの要素が入ったリストを返します。月は 0 から 11 で、年は1900年から何年経過したか(2006年なら106)、曜日は 0 から 6 で、それぞれ返ってくるので注意しなくてはいけません。
また、上の例文では引数として time 関数の返り値を渡していますが、引数を省略した場合 localtime() でも同等です。time 関数は現在のエポック秒を返す関数なので、localtime(time) もしくは localtime() はローカルゾーンで表した現在の日時を返す事になります。


一般に配布されている CGI で localtime() を使う時にリスト代入を利用して一旦全ての要素を個別のスカラ変数に代入してから、利用しているのをよく見かけます。上の perldoc のコードでもそうなっていますね。
ただ、例えば「今日は何月何日か知りたい」だけなのに、必要のない時分秒や年や曜日までスカラ変数に格納するのは無駄ですし、見通しが悪くなるとも考えられます。
H.I.さんのコメントによると「undef」を使えば不必要な要素は受け取らなくて済みます。例えば「今日は何月何日なのか知りたい」という場合は、以下のように書けるわけですね。

use strict;
 
my (undef, undef, undef, $day, $month) = localtime();
my $dname = day_name($day);
my $mname = month_name($month);
print "It is the $dname of $mname today.\n";
 
sub day_name {
    my $day = shift;
    my @th = ("th", "st", "nd", "rd");
    my $n = $day % 10;
    return $day . ($n < 4 ? $th[$n] : "th");
}
sub month_name {
    my $month = shift;
    my @names = ("Jan", "Feb", "Mar", "Apr", "May", "Jun",
                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
    return $names[$month];
}

localtime の代入文の左辺にあるリストで undef を使って、時分秒の変数を省略しています。また、右辺のリストの要素数に対して、左辺のリストの要素数が少ない場合、残りの要素は捨てられます。
ただ日付を表示するだけじゃ面白くなかったので、day_name と month_name 関数を作ってみました。(^_^) day_name は「日」の後ろに「th」や「st」「nd」などを1桁目の数字($$n)に沿って追加して返します。month_name は「月」を英語で表した文字列を返します。
実行結果は以下の通りです。

It is the 30th of May today.

ちゃんと今日の月日が表示されました。
試しに、コンピュータ内の日時を「5月1日」に変えてみると

It is the 1st of May today.

と表示されました。(^_^)
スライスを利用する方法もあります。

undef

undef について解説するのを忘れていました。(^_^;)
undef 関数は本来「変数(左辺値)を未定義にする」関数です。未定義にされた変数は「定義されていないもの」として扱われます。変数が定義されているか調べて真偽を返す defined という関数がありますが、undefined された変数は偽になります。

use strict;
 
my $hello = "Hello!";
print defined($hello) ? $hello : "undefined!", "\n";
 
undef $hello;
print defined($hello) ? $hello : "undefined!", "\n";

実行結果は以下の通り。

Hello!
undefined!

また、undef 関数は常に「未定義」(undefined)という意味の値を返します。引数を省略しても、この「未定義」という値が返ってきます。この「未定義値」(undef と呼ばれる)は、現在のところサブルーチンの返り値や、引数として渡したり、変数に代入したりできます。
ただし、undef で返される「未定義値」は、Read-only (読み込み専用)なので書き換えようとするとエラーがでます。

undef = "Hello!";
Modification of a read-only value attempted at undef2.pl line 1.

なので、本来は左辺値として使えないはずなのですが、リスト代入では利用できるので、「例外」というわけですね。
とっても勉強になりました。ありがとうございました。(^_^) > H.I.さん