追記あり。そんなに問題ないのかもしれない。

Perl Best Practice の 5.12 では、ハッシュスライスを使って、やや大きな配列データを作成するときに 中間のハッシュを作ることを推奨しています。その際に、keys と values を使っています。

こんな感じです。

5.12 スライスのファクタリング
スライスのキーやインデックスの数が多い場合はファクタリングする
Readonly my %CORRESPONDING => (
    'top'       => -1,
    'prev'      => -2,
    'backup'    => -3,
    'emergency' => -4,
    'spare'     => -5,
    'rainy day' => -6,
    'alternate' => -7,
    'default'   => -8,
);

@frames[ values %CORRESPONDING ] = @active{ keys %CORRESPONDING };

んで、これは今までは良かったと思うのですが、Perl 5.18 からは Hash Key Randomization が導入されています。

keys とか values の結果は実行するたびに異なる順序で値が返るようになりました。

で、ここで問題です。PBP で取り上げられているこのコードは、最近の Hash Key Randomization が採用されている Perl でも動作するでしょうか?

これが、動作するんですよね。keys を実行すると、実行するたびにキーが返る順序は変わるのですが、keys と対になる values は同じ順序で返ってくるみたいです。

でも、これって、言語仕様のエッジケースをつくような悪いコードじゃないかな、と思います。今は動くけど、将来の動作保証されるか良くわかんないし、そもそも、「keys 実行してからの values は同じ順序で返ることが保証される」ってのは、なまじ Hash Key Randomization の事を知ってたりするとちょっと直感に反する気もするし。なので、この Best Practice は Best Practice ではなくなってしまったのかなぁ、と思います。

かっこわるいけど、foreach 使ってループまわして同等の処理するのが無難じゃないかなぁ、と思ってます。

追記(2013/01/03)

gfx さんよりコメントいただきました

Fuji, Goro • 4 months ago
このPBPの例は保証されてますよ。perldoc -f keysにも"So long as a given hash is unmodified you may rely on "keys", "values" and "each" to repeatedly return the same order as each other." (対象のハッシュを変更しない限り、keys, values, eachは何度繰り返してもそれぞれ同じ順番で値を返す)とありますし、エッジケースではありません。ただし、対象のハッシュが違うとだめ(@frames[ values %a ] = @active{ keys %b } はNG)ですが。

もともとhash randomizationはセキュリティのために導入されたもので、あるkeyの集合から内部的にどう保存されるかを予測できないようにするだけなのです。いたずらにkeys/values/eachを使いにくくしようというものではないのです。

※ なお、当blogのコメント設定をミスっていたのを直した関係で、今はこのコメント見れなくなってます。すいません。。。

確かに、ハッシュをいじらない場合は、keys は常に同じ値を返すようですね。(実行するたびに順番は変わるが、@keys1 と @keys2 は常に同じ順番になる)。これなら問題ないのかなぁ。

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

my %hash_dayo = (
    aaa => 111,
    bbb => 222,
    ccc => 333,
    ddd => 444,
);

my @keys1 = keys %hash_dayo;
my @keys2 = keys %hash_dayo;

warn Dumper(\@keys1, \@keys2);
# => $VAR1 = [
#           'ddd',
#           'bbb',
#           'aaa',
#           'ccc'
#         ];
# $VAR2 = [
#           'ddd',
#           'bbb',
#           'aaa',
#           'ccc'
#         ];