BG

each 関数

each を使ったループを途中で抜けると反復子(iterator)が初期化されない話。

my %hash = map {$_ => 1} qw/a b c/;

while( my($k, $v) = each %hash ) {
    print "1st loop\n";
    last;
}

while( my($k, $v) = each %hash ) {
    print "2nd loop\n";
}

このコードを実行すると

予想:
1st loop
2nd loop
2nd loop
2nd loop

結果:
1st loop
2nd loop
2nd loop

予想より, 2 回目のループの結果が 1 回少ないのですが, 結論は "perldoc -f each" に記述されていました。

There is a single iterator for each hash, shared by all "each", "keys", and "values" function calls in the program; it can be reset by reading all the elements from the hash, or by evaluating "keys HASH" or "values HASH".

ハッシュごとに反復子を持つため, 途中でループを抜けかつ次に最初から繰り返したい場合は, 反復子を初期化する必要があります。

my %hash = map {$_ => 1} qw/a b c/;

while( my($k, $v) = each %hash ) {
    print "1st loop\n";
    last;
}

keys %hash;    # 初期化が必要
while( my($k, $v) = each %hash ) {
    print "2nd loop\n";
}

# ドキュメントはちゃんと読めと :-)


perl 5.13.1 のソースで確認です。

==== pp.c ====
4619 PP(pp_each)
4620 {
4621     dVAR;
4622     dSP;
4623     HV * hash = MUTABLE_HV(POPs);
4624     HE *entry;
4625     const I32 gimme = GIMME_V;
4626 
4627     PUTBACK;
4628     /* might clobber stack_sp */
4629     entry = hv_iternext(hash);
4630     SPAGAIN;

each に対応するのが pp_each なので, その中で呼ばれている hv_iternext (4629行) を見ると

==== hv.h ====
398 #define hv_iternext(hv) hv_iternext_flags(hv, 0)

==== embed.h ====
2717 #define hv_iternext_flags(a,b) Perl_hv_iternext_flags(aTHX_ a,b)

==== hv.c ====
2102 HE *
2103 Perl_hv_iternext_flags(pTHX_ HV *hv, I32 flags)
2104 {
2105     dVAR;
2106     register XPVHV* xhv;
2107     register HE *entry;
2108     HE *oldentry;
2109     MAGIC* mg;
2110     struct xpvhv_aux *iter;

(snip)

2125     iter = HvAUX(hv);

HvAUX (2125行) が反復子(xpvhv_aux) を取得するマクロになっており

238 #define HvARRAY(hv) ((hv)->sv_u.svu_hash)
239 #define HvFILL(hv)  ((XPVHV*)  SvANY(hv))->xhv_fill
240 #define HvMAX(hv)   ((XPVHV*)  SvANY(hv))->xhv_max
241 /* This quite intentionally does no flag checking first. That's your
242    responsibility.  */
243 #define HvAUX(hv)   ((struct xpvhv_aux*)&(HvARRAY(hv)[HvMAX(hv)+1]))

その定義からするとハッシュ要素格納用領域の後ろ HvMAX(hv)+1 番目に, そのハッシュの反復子が格納されている様です。


一方, keys(pp_keys), values(pp_values) は

==== mathoms.c ====
733 /* Ops that are calls to do_kv.  */
734 PP(pp_values)
735 {
736     return do_kv();
737 }
738 
739 PP(pp_keys)
740 {
741     return do_kv();
742 }

==== doop.c ====
1429 OP *
1430 Perl_do_kv(pTHX)
1431 {
1432     dVAR;
1433     dSP;
1434     HV * const hv = MUTABLE_HV(POPs);
1435     HV *keys;

(snip)

1452     keys = hv;
1453     (void)hv_iterinit(keys);   /* always reset iterator regardless */

その実行に際して, hv_iterinit (1453行) が呼ばれます。

==== embed.h ====
2713 #define hv_iterinit(a)     Perl_hv_iterinit(aTHX_ a)

==== hv.c ====
1922 I32
1923 Perl_hv_iterinit(pTHX_ HV *hv)
1924 {

(snip)

1932     if (SvOOK(hv)) {
1933         struct xpvhv_aux * const iter = HvAUX(hv);
1934         HE * const entry = iter->xhv_eiter; /* HvEITER(hv) */
1935         if (entry && HvLAZYDEL(hv)) {  /* was deleted earlier? */
1936             HvLAZYDEL_off(hv);
1937             hv_free_ent(hv, entry);
1938         }
1939         iter->xhv_riter = -1;  /* HvRITER(hv) = -1 */
1940         iter->xhv_eiter = NULL; /* HvEITER(hv) = NULL */
1941     } else {
1942         hv_auxinit(hv);
1943     }

1939,1940 行にて iter(反復子) の初期化が行われています。
勉強不足で SvOOK (1932行) が何をしているのか理解できなかったのですが, SvOOK が偽であり hv_auxinit (1942行) が呼ばれた場合でも

==== embed.h ====
3644 #define hv_auxinit     S_hv_auxinit

==== hv.c ====
1879 static struct xpvhv_aux*
1880 S_hv_auxinit(HV *hv) {
1881     struct xpvhv_aux *iter;
1882     char *array;

(snip)

1897     iter = HvAUX(hv);
1898
1899     iter->xhv_riter = -1;  /* HvRITER(hv) = -1 */
1900     iter->xhv_eiter = NULL;    /* HvEITER(hv) = NULL */
1901     iter->xhv_name = 0;
1902     iter->xhv_backreferences = 0;
1903     iter->xhv_mro_meta = NULL;

と, iter を初期化する処理 (1899 - 1903行) が実行される様です。


また, hv_iterinit を呼び出している所に戻ると

==== doop.c ====
1429 OP *
1430 Perl_do_kv(pTHX)
1431 {

(snip)

1453     (void)hv_iterinit(keys);   /* always reset iterator regardless */
1454 
1455     if (gimme == G_VOID)
1456         RETURN;
1457 
1458     if (gimme == G_SCALAR) {
1459         IV i;

'gimme == G_VOID' (1455行) が真の場合, 即ち 'void context' で実行された際は, 反復子の初期化を行った直後に処理が終了しているため, 配列(keys, values の戻り値)の作成が行われません。

  1. keys, values はハッシュの要素数分だけ配列を作ってしまうので, 要素数が多い場合にパフォーマンスが悪くなる
  2. その場合, each の方がパフォーマンス的に優れている
  3. でも, (反復子を初期化するため) keys or values を呼び出したら意味が無いのでは?

との疑問がありましたが, それも問題ありませんでした。

参考: