ハンドラの中からハンドラ自身を呼び出す事を再帰呼び出しといいます。こうすることで簡単に繰り返し構造ができ上がるというわけです。
関数型の言語では初歩的な技術として普通に使われるものですから、きちんと基本を押さえれば簡単に修得できるでしょう。
次のハンドラは漸化式を表すものです。ここでの漸化式は、f(n) = n*(n-1)*(n-2)*...2*1、という式です。
getFact(4) on getFact(n) if n = 1 then return 1 return n * (getFact(n - 1)) end getFact
これが、再帰呼び出しの基本的な流れです。
値を加工する部分でカウンタを減少させ、再帰終了条件を「カウンタが0や1になるまで」とするのが一般的です。
「16進数と10進数の変換」の部分でも使っているので、参考にしてみてください。
再帰呼び出しでは、returnにより一つ前に戻っていきますから、ハンドラがどこで呼び出されたか記憶しておく必要があり、スタックというデータ構造で記録しています。
AppleScriptの場合は、この記憶領域はあまり沢山用意されているわけでは無く、わりと簡単に「スタックあふれ」のエラーがおこります。
環境によって多少違いがあるかもしれませんが、以下のハンドラのように引数を285以上にするとエラーが発生します。
getSum(285) on getSum(n) if n = 1 then return 1 return n + (getSum(n - 1)) end getSum
つまりAppleScriptの場合は、ループ回数が大きい場合に再帰呼び出しを使うことはできないということになります。
再帰構造はrepeatとif文の組み合わせで書き換えることができるので、そちらにしたほうが、変数が幾つも必要になったりして多少不格好ですが、呼び出し処理が発生しないので高速に動作します。
getSum(285) on getSum(n) set theSum to 0 repeat with i from 1 to n set theSum to theSum + i end repeat return theSum end getSum
今までの例を見ると、すくなくともAppleScriptでは再帰処理の出番は無いように思われますが、木(tree)構造を持ったデータの処理を行う場合、再帰呼び出しを利用する方が簡単にカタがつきます。
代表的な木構造のデータはFinderのフォルダ構造があります。
Finderのcontainerオブジェクトのentire contents属性を使えば、再帰処理を使わなくても処理できますが、バグで正常に動かないバージョンもあります。
そんなわけで以下のスクリプトは、階層下の全てのフォルダをエイリアスのリストとして取り出します。処理速度はイマイチなので、あしからず。
set folderList to {} entireFolders(choose folder, folderList) folderList -- このリストを使って処理をすればOK on entireFolders(everyFolder, folderList) tell application "Finder" repeat with curFolder in everyFolder set end of folderList to curFolder as alias -- これで、参照元のデータが書き換えられる my entireFolders(a reference to folders in curFolder, folderList) end repeat end tell end entireFolders
リストが参照渡しされることを利用して値の受け渡しを行っていますので、返り値がないハンドラですが、folderListの中身はちゃんと変わっています。これについて詳しくは「引数の参照渡し」を御覧下さい。
再帰呼び出し終了の判定は「repeat with curFolder in everyFolder」でeveryFolderが{}の場合ループが起きないので、再帰呼び出しが行われないことを利用しています。詳しくは「ループの基本」を参照して下さい。
先ほどの例では、階層下のフォルダをリストにして取り出していましたが、再帰呼び出しハンドラの中で処理してしまう方法も考えられます。
以下のスクリプトでは、ラベルを全て同じものにしています。
execAllFolders(choose folder) on execAllFolders(curFolder) tell application "Finder" set itemList to items of curFolder repeat with curItem in itemList if class of curItem = folder then my execAllFolders(curItem) -- フォルダならば再帰呼び出し else set label index of curItem to 5 -- ラベルを変更 end if end repeat end tell end execAllFolders
再帰呼び出しは基本的にrepeatによるループ構造に書き換える事ができるので、知らないとプログラムが書けないという事はないのですが、再帰呼び出しを使うと、変数が少なくエレガントにスクリプトが書ける傾向があります。
少なくとも、人が書いたスクリプトに再帰呼び出しが登場した場合に理解できない、という事がないようにしておいた方がいいでしょう。
ところで、階層下の全フォルダを処理する場合、AppleScriptではよりエレガントな方法があります。
詳しくは「複数参照」を御覧下さい。