このブログは、旧・はてなダイアリー「檜山正幸のキマイラ飼育記 メモ編」(http://d.hatena.ne.jp/m-hiyama-memo/)のデータを移行・保存したものであり、今後(2019年1月以降)更新の予定はありません。

今後の更新は、新しいブログ http://m-hiyama-memo.hatenablog.com/ で行います。

ディレクトリツリー生成をCatyスクリプトで書いてみる

repeatではなくて、普通の再帰を使ってみる。これはスタックフレームを消費する。名前付きコマンドを許してしまうと、repeatがあっても結局コールスタックベースの再帰は必要になる。


type path = string(format=path);

type Item = {"name" : string, "isDir" : boolean, *:any?};

type Tree = {
"name" : string,
"child" : [Tree*]?
};

command tree [path] :: void -> Tree {
do {
%1 | path:basename >: "name",
lsdir %1 |
each{ [%1, pass] | make-tree }>: "child"
}
};

command make-tree :: [path, Item] -> Tree {
set { nth 1 >parent, nth 2 >- }|
when isDir {
false ==> // ファイル、終端
do {
pv name >: "name"
},
true ==> // ディレクトリ、再帰
[%parent, pv name > name] | path:join > new_path;
do {
%name >: "name",
lsdir %new_path |
each { [%new_path, pass] | make-tree } >: "child"
}
}
};

make-tree呼び出しを単純にrepeatに置き換えることはできない。eachが暗黙のbeginを含むので、eachスコープを超えて制御を戻せない。begin { [%new_path, pass] | repeat }は無限ループになるので、each { [%new_path, pass] | repeat } も無限ループ。

make-treeを全面的に書き換えれば、repeatに(つまり末尾再帰に)できるが、そうやってもメモリの節約にはならない。スタックは使わないが、入力がスタックの代わりに膨らむので同じことだ。再帰が時間方向だけじゃなくてデータ方向にも膨らむ過程なら、末尾再帰に直して最適化してもメモリを食いつぶす。

末尾再帰で無限に走れるのは、記憶が蓄積されないで時間だけを消費する繰り返し。なにかを忘れない限りは無限には繰り返せない。スタックベースの再帰とrepeatが決定的に違う状況は、空間有限、時間無限で走るとき。