時計を壊せ

駆け出してからそこそこ経ったWebプログラマーの雑記

コンテキストとハッシュの怪

怖い話

割とハマりがちなので。
たとえば、以下のようなコードがあったとします。

use strict;
use warnings;
use utf8;

use Data::Dumper;

sub yyy {
    my $key = shift;
    return unless $key;

    return "yyy_$key";
}

my $hashref = {
    xxx => yyy(),
    aaa => 'bbb',
    ccc => 'ddd',
};

print Dumper $hashref;

上のサンプルコードの出力は以下のようになります。

$VAR1 = {
'bbb' => 'ccc',
'xxx' => 'aaa',
'ddd' => undef
};

マテマテマテマテって思うかもしれませんが、perlのバグなどではなく正常な挙動です。

コンテキスト

Perlにはコンテキストと呼ばれる概念があります。
これは、値の解釈によって処理を変えるための概念です。
詳しい説明は初めてのPerl*1を読むか、こことかこことかを読むと良いかもしれません。

Perlは値をスカラーコンテキストで評価する場合とリストコンテキストで評価する場合があり、それぞれで処理内容が変わる場合があります。
例えば変数への代入の場合はスカラー型の変数への代入の場合は値をスカラーコンテキストで評価します。

例1:

use strict;
use warnings;
use utf8;

my $calar1 = 'aaa';                 ## スカラーをスカラーコンテキストで評価
my $calar2 = ('xxx', 'yyy', 'zzz'); ## リストをスカラーコンテキストで評価(リストの最後の値が得られる)

print $calar1, "\n"; # 'aaa'
print $calar2, "\n"; # 'zzz'

アレイ型/ハッシュ型変数への代入の場合は値をリストコンテキストで評価します。
例2:

use strict;
use warnings;
use utf8;

use Data::Dumper;

# ARRAY
my @rray1 = 'aaa';                 ## スカラーをリストコンテキストで評価(与えたスカラー値が1つだけ入ったリストが得られる)
my @rray2 = ('xxx', 'yyy', 'zzz'); ## リストをリストコンテキストで評価

# HASH
my %ash1  = 'aaa';                 ## スカラーをリストコンテキストで評価(与えたスカラー値がkeyとなりvalueがundefなハッシュが得られる)
my %ash2  = ('xxx', 'yyy', 'zzz'); ## リストをリストコンテキストで評価

print Dumper \@rray1; # ['aaa']
print Dumper \@rray2; # ['xxx', 'yyy', 'zzz']

print Dumper \%ash1; # { 'aaa' => undef }
print Dumper \%ash2; # { 'xxx' => 'yyy', 'zzz' => undef }

また、更に変な書き方として、リストの中にリストを含む事も出来ます。
これは結合され1つのリストとして解釈されます。

('xxx', 'yyy', ('aaa', 'bbb')); # => ('xxx', 'yyy', 'aaa', 'bbb')

ハッシュ内では値はリストコンテキストで評価します。
なので、一番上の何も返さない関数の場合、空のリストとして解釈されてキーがズレてしまうのです。

{
    xxx => (),
    aaa => 'bbb',
    ccc => 'ddd',
}; ## { xxx => 'aaa', bbb => 'ccc', ddd => undef }

この場合、ほぼプログラマの思い通りの挙動にはならないでしょう。怖いですね。

こわいのこわいのとんでけー

上のコード類を実行してみた人は以下のような警告が出るのに気づいたかもしれません。

Odd number of elements in hash assignment

これはハッシュとして与えたリストが奇数だった場合に警告してくれています。
上のような間違ったコードを書いてしまっている場合がある事を危惧して(かどうかは知りませんが)教えてくれているのです。
なので警告は/dev/nullには決して捨てたりせずに*2、不可解な挙動が起こった場合は警告も含め怪しいメッセージが出ていないか確認するとよいでしょう。

*1:最近第6版が発売されたようですね

*2:捨てると椅子が飛んできます