Subscribed unsubscribe Subscribe Subscribe

ハッシュの初期化の注意点

perl
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my $hashref = {
    hoge => 100,
    fuga => 200,
    piyo => 300,
};

print Dumper $hashref;

と、ハッシュ初期化時にkeyとvalueの組み合わせをまとめて指定することがあると思います。

$ perl hoge.pl
$VAR1 = {
          'piyo' => 300,
          'fuga' => 200,
          'hoge' => 100
        };


ここで、valueの部分にサブルーチンやメソッドからの返り値を入れるようにするときに注意。

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my $hashref = {
    hoge => 100,
    fuga => foo(),
    piyo => 300,
};

print Dumper $hashref;

sub foo {
    return 200;
}

これなら問題なく動くのだけど、ここでfoo()が複数要素を持つ配列を返すと…

sub foo {
    return (200, 250);
}
Odd number of elements in anonymous hash at hoge.pl line 6.
$VAR1 = {
          '250' => 'piyo',
          'fuga' => 200,
          'hoge' => 100,
          '300' => undef
        };

こんなことになってしまう。
要は、ハッシュの初期化ってリストを渡して、(0から始めて)偶数番目の要素をkey、奇数番目の要素をvalueとする仕組みなので、foo()が返す要素数によって初期化時に使われるリストの要素数が変わってしまうとおかしくなる、ということ、かな?
何も返さない場合もズレてしまう。

sub foo {
    return;
}
Odd number of elements in anonymous hash at hoge.pl line 6.
$VAR1 = {
          'fuga' => 'piyo',
          'hoge' => 100,
          '300' => undef
        };


なので、このような初期化を行うときにはサブルーチンやメソッドの返り値を使わないようにする、とか、必ず1つのscalar値を受け取るようにする、とかなんか気をつけた方が良いかもしれない。

my $hashref = {
    hoge => 100,
    fuga => scalar(foo()),
    piyo => 300,
};

もっとベストなプラクティスがあるような気がする…

デフォルト値の話

ところで、上記の例でfoo()がundefを返す場合はデフォルト値を設定するように、という場合

my $hashref = {
    hoge => 100,
    fuga => foo() || 200,
    piyo => 300,
};

とかやってしまいそうだけど、この場合

sub foo {
    return 0;
}
sub foo {
    return "0";
}
sub foo {
    return '';
}

とかの場合もfoo()の返り値は偽とみなされデフォルト値200が設定されてしまう。
「undef以外の場合だけデフォルト値200にしたい」という場合は

my $val;
my $hashref = {
    hoge => 100,
    fuga => defined($val = foo()) ? $val : 200,
    piyo => 300,
};

とか、こんなカンジ? perl 5.10以降だと"//" (the defined‐or operator)を使って

my $hashref = {
    hoge => 100,
    fuga => foo() // 200,
    piyo => 300,
};

が同じ意味になるのかな?

"return;"と"return undef;"は識別できる?

別にvalue値にundefを設定してもいいのだけど、foo()の返り値が無い場合(何も返さないreturnの場合)だけデフォルト値にしたい、ということは出来るのだろうか?
つまり下記のテストが通るようにしたい、という場合?

#!/usr/bin/perl
use strict;
use warnings;
use Test::More tests => 5;

sub hoge {
    my $code = shift;
    return $code->() // 200;
}

is(hoge(sub { return 0;   }), 0  );
is(hoge(sub { return '';  }), '' );
is(hoge(sub { return '0'; }), '0');
ok(! defined(hoge(sub { return undef; })));
is(hoge(sub { return;     }), 200);

この場合だと4番目がhoge(undef)の返り値がundefじゃなくなるためNG。

sub hoge {
    my $code = shift;
    my @a;
    return scalar(@a = $code->()) ? $a[0] : 200;
}

こうすれば一応通るけど、キモい。
"return;"と"return undef;"を区別したいなんていう場合はそうそう無いと思うけど、どうすれば良いのかは気になる。。。