@kyanny's blog

創造性は記憶によるところが大きい - 黒澤明

続・再帰?は難しい - 教えてもらったコードを書いて動かす

再帰?は難しい - 刺身☆ブーメランのはてなダイアリー で関数の再帰呼び出しが難しいとこぼしていたら id:amachang さんが 再帰で local を使う - IT戦記 というエントリで教えてくれました。ありがとうございます。

読んでもやっぱり頭の中で追えなかったので、コピペして動かしてみたらなんとなくわかったような気がしてきました(わかった、といえるほど自信はない。。)

「再帰の中で最初の一回だけ [] が作られる local なので、再帰を抜けると消える」の一文がわからなくて、 print してみたら、同じ配列リファレンスのようなので「最初の一回だけ」という意味がわかった。

sub hash_recursively {
  # 省略

  # 再帰の中で最初の一回だけ [] が作られる
  # local なので、再帰を抜けると消える
  print "a ", $main::_links, "\n";
  local $main::_links = $main::_links || [];
  print "b ", $main::_links, "\n";

  # 省略
}

a 
b ARRAY(0x81c538c)
a ARRAY(0x81c538c)
b ARRAY(0x81c538c)
a ARRAY(0x81c538c)
b ARRAY(0x81c538c)
a ARRAY(0x81c538c)
b ARRAY(0x81c538c)
a ARRAY(0x81c538c)
b ARRAY(0x81c538c)

次の抜けると消える、というのは、関数の外で $_links を宣言するのと等価で、「それがなんだか気持ち悪い」と書いたのに対して「中で local 宣言すれば良い」ってこと、で、あっているのかなぁ・・・。「気持ち悪い」というのは、このすっきりしたコードを読むとそんなに感じないのだけど、自分が書いていたほうは実は sub {} の先頭に ライブドアブログ(livedoor Blog)| 読みたいブログが見つかる のような「動的にパッチを当てるコード」が山ほどあってかなり見通しが悪くなっていて、だから宣言なしにいきなり @links とかが出てくるのに書いてて違和感があったのでした。

local で宣言するスコープの話は、先月ブログで流行ったときにちらっとみても難しくてよくわからないので避けていたんですが、やっぱりちゃんと勉強しないとダメですね。「そこまで複雑なスコープを普段は必要としないだろう」と思っていたけど、わかってないと結局どこかで困るということか。 local 宣言のところだけじゃなく、下のほうのデータ構造を「ならして」作り直すあたりも、自分が書いたのと比べるとずいぶんスマートでわかりやすく見えました。


id:fbis さんにもブックマークで引数に@linksのリファレンス渡すか、オブジェクトにしてメンバにデータを持つかすればいいかと思った。とコメントされていたので、それも書いてみました。

use strict;
use Data::Dumper;

sub hash_recursively {
  my $category = shift;
  my $category_name = shift || 'category_global';
  my $links = shift || [];

  while (my($key, $value) = each %$category) {
    if (ref($value)) {
      hash_recursively($value, $key, $links);
    }
    else {
      push(@$links, [$key, $value, $category_name]);
    }
  }
  return $links;
}


my $links = hash_recursively({
  category_a => {
    bookmark_a => 'http://hoge.org',
    bookmark_b => 'http://fuga.org',
  },
  category_b => {
    bookmark_c => 'http://hoge.com',
    bookmark_d => 'http://fuga.com',
    category_c => {
      bookmark_e => 'http://hoge.net',
      bookmark_f => 'http://fuga.net',
    },
    category_d => {
      bookmark_g => 'http://hoge.jp',
      bookmark_h => 'http://fuga.jp',
    },
  },
});

print Dumper($links);

ダンプされた結果は先のコードと同じなのでこれであってるかなぁ。言われてみればなるほどなんですがこういうのが最初から思いつくか思いつかないかが差なんだろうなあ。