イテレータ
Repeat With Eachを使ったイテレーション
イテレータから与えられる各値を扱い、その値を使って何らかのアクションを実行する必要があるときは、repeat with eachループを使用します。次に示すのは、リストからの各値を使ったループの例です。
repeat with each color in ("red", "orange", "yellow", "green")
put "I think " & color & " is a very pretty color."
end repeat
範囲のすべての値にわたるイテレーションも、リストのときと同じようにrepeat with eachを使用します。
repeat with each letter in "A" to "G"
repeat with each digit in 1 to 3
put letter & digit-- A1, A2, A3, B1, B2, ...
end repeat
end repeat
Each式を使ったイテレーション
リストまたは他のイテレータからの各値(または選択対象の値のセット)を処理して、結果の値のリストを生成する必要があるときは、each式が非常に効果的と言えます。each式の結果は常にリストになります。Each式をご覧ください。
NextValueを使ったイテレーション
The nextValue()関数は、続きの次の値をイテレータから返します。シーケンスが尽きて、そのイテレータから利用できる値がなくなったら、特殊な定数値であるendが返されます。
put 100 to 200 by 50 into range
put range's nextValue -- 100
put range's nextValue -- 150
put range's nextValue -- 200
put range's nextValue -- ⓔ ⓝ ⓓ
CurrentIndexを使ってイテレーションを調整する
リストも範囲も、currentIndexプロパティを備えています。リストまたは範囲が最初に作成されたときのcurrentIndexの値はゼロです。そのイテレータに対するrepeat with eachループまたはeach式の開始時にも、currentIndexの値はゼロにリセットされます。
イテレーションの間、イテレータから値が取り出されるたびに、currentIndexプロパティはインクリメントされます。イテレータのcurrentIndexにアクセスして、元のリストまたは範囲における現在の値のアイテム番号を明らかにすることも可能です。
set foo to (a,b,c,d,e)
put foo's currentIndex & each item of foo -- (1a,2b,3c,4d,5e)
さらに、currentIndexに変更を加えることにより、イテレーションの過程を変更することができます。例えば、このrepeatループでは、範囲内の奇数だけが表示されます。
set myRange to 1..100
repeat with each item of myRange
put it
add 1 to myRange.currentIndex -- 次のアイテムをスキップします
end repeat
イテレーション中にリストを変更する
repeat with each、each式またはnextValue関数を用いたリスト内のアイテムにわたるイテレーションの間、SenseTalkはリストのcurrentIndexプロパティを使ってイテレーションを常に追跡しています。リスト内でアイテムの挿入、削除または置き換えが行われたときには必ず、currentIndexプロパティが適宜調整され、変更が反映されます。したがって、イテレーションの最中でもアイテムの追加、削除または置き換えが問題なく行えます。
カスタムイテレータ
イテレータとして最も一般的に使われるソースは、リストと範囲です。これ以外にも、次の値を続けざまに返すnextValueハンドラを備えたスクリプトまたはオブジェクトを作成すると、カスタムイテレータを作ることができます。返される値は何でも構いません。実装は、そのオブジェクトのプロパティであるリストからnextValueを返すようなシンプルなものも可能ですし、もしくは、オブジェクトが保持している内部状態から次の結果を計算することもできます。このタイプのイテレータは、必要に応じて値をその都度生成するため、「ジェネレータ」と呼ばれることがあります。
イテレータとして使用するオブジェクトには、従わなければならないルールがいくつかあります。まず、objectType(オブジェクトタイプ)を「iterator(イテレータ)」としなければなりません。これで、repeat with eachループまたはeach式において各値を得る際にオブジェクトのnextValueハンドラを呼び出すよう、SenseTalkに指示します。次に、当然ですが、各値を次々に与えるnextValueハンドラを備えていなければなりません。nextValueハンドラは、シーケンスが終わりに達し、利用できる値がなくなったときに、値endを返す必要があります。そうでなければ、ある時点でループから脱するためのexit repeat文をrepeatループに入れてください。そうしないと、ループが永遠に続いてしまいます。each式には、最終的にendを返さないイテレータは使用しないでください。
カスタムイテレータは、nextValueハンドラの他に、startIteration(イテレーションの開始)ハンドラをオプションで備えることもできます。これがあると、repeat with eachループまたはeach式によってnextValueが呼び出される前に、このstartIterationハンドラが呼び出され、イテレータによる初期条件のセットアップや、前のイテレーション後の状態のリセットが可能になります。
次に示すカスタムイテレータの例では、それぞれの値として前の2値の合計を取るフィボナッチ数列の値を生成しています(ここで、フィボナッチ数列は無限ですが、このイテレータは1万以下の値だけを返すため、each式と一緒に使うことができます)。
set FibonacciGen to {a:0, b:1, objectType:"iterator", script:{{
to handle nextValue
if my b is more than ten thousand then return end
set (my a, my b) to (my b, my a + my b)
return my a
end nextValue
}} }
set fib to a new FibonacciGen
repeat 10
insert fib's nextValue into firstTen
end repeat
put firstTen -- (1,1,2,3,5,8,13,21,34,55)
put fib.nextValue -- 89
put each item of fib -- (144,233,377,610,987,1597,2584,4181,6765)
イテレータをパラメータとして渡す
イテレータは、パラメータとして別のスクリプトやハンドラに渡すことができます。リストまたは範囲のcurrentIndexプロパティの値やカスタムイテレータのプロパティなど、現在の状態でそのまま渡されます。したがって、ローカルのスクリプトでイテレータから(nextValueを使って)1つずつ値を使用していた場合、呼び出したスクリプトではシーケンス内の後続の値を引き続き使用することができます。
なお、SenseTalkは通常、あらゆるパラメータ値のコピーを作成するため、呼び出したスクリプトはイテレータのそれ自身のコピーを受け取ることに留意してください。ローカルスクリプトと呼び出したスクリプトの両方がイテレータからの単一の値順序を利用するようにイテレータを分け合うには、イテレータを参照で渡します。例:
set takeANumber to 1..20
put takeANumber's nextValue-- 1
put takeANumber's nextValue -- 2
scriptThatTakesTwoNumbers @takeANumber -- 3と4を取ります
put takeANumber's nextValue -- 5
「@」なしでtakeANumberを参照で渡すと、最後のtakeANumberのnextValue呼び出しにおいて、5ではなく3が返されます。ここで参照を渡すことにより、呼び出したスクリプトに対してコピーを切り離すのではなく、両方のスクリプトが同一の値順序を分け合います。
値(受け取ったパラメータなど)が繰り返し可能かどうかはis an iterator演算子を使ってテスト可能です。
if param(1) is not an iterator then throw "An Iterator Is Required"
イテレーションを再スタートする
範囲またはリストにわたるイテレーションを最初の値から再スタートするには、currentIndexをzeroに設定します。次にnextValueにアクセスしたときには、範囲またはリストの最初の値が返されます。
set the currentIndex of sequence to zero -- イテレーションをリセットします
カスタムイテレータには、イテレーションの再スタートメカニズムを設けることも、設けないこともできます。repeat with eachループやeach式で再び利用できる設計のカスタムイテレータには、リセット用に呼び出せるstartIterationハンドラを実装すべきです。その他のカスタムイテレータには、リストまたは範囲と同じようにリセットできるcurrentIndexプロパティを実装することもできます。ただし、基本的なイテレータには、どちらも必要ありません。
リスト値の割り当て
SenseTalkにおいては、リストのそれまでの範囲を超えてアイテムが挿入されたり、アイテムに値が割り当てられたりすると、リストが動的に拡張されます。このため、リスト内で広く散らばったアイテムにも値を割り当てることが可能です。例:
set list to("a","b")
put "g" into item 7 of list
put "z" into item 26 of list
put list
-- (a,b,,,,,g,,,,,,,,,,,,,,,,,,,z)
この例のリストには、26個のアイテムがありますが、そのうち値が割り当てられているのは4個にすぎません。その他のアイテムは空です。このリストのアイテムにわたるイテレーションを行うと、26個すべてのアイテムにアクセスが行われます。状況によっては、値が明示的に割り当てられているリストアイテムにのみアクセスできる方が都合が良い場合もあります。そのためにあるのがnextAssignedValue関数です。この関数の働きは、nextValue関数と似ていますが、currentIndexプロパティを単に1ずつインクリメントしていってそのインデックスの値を返すのではなく、割り当てられた値を持つリスト内の次のアイテムまでcurrentIndexを進ませ、その値を返します。したがって、先ほどの例だと、次のようになります。
put list's nextAssignedValue -- a
put list's currentIndex -- 1
put list's nextAssignedValue -- b
put list's currentIndex -- 2
put list's nextAssignedValue -- g
put list's currentIndex -- 7
put list's nextAssignedValue -- z
put list's currentIndex -- 26
put list's nextAssignedValue -- ⓔ ⓝ ⓓ
nextValue関数とnextAssignedValue関数はどちらも、リストのcurrentIndexプロパティを進ませるため、この2つの関数呼び出しを混在させて、必要に応じてリスト内の次の連続値(割り当ての有無を問わず)を得たり、現在のインデックスの次に割り当てのあるリスト内の値を得たりすることができます。
注:empty値は割り当てのない値と同じではありません。リストアイテムにempty値が格納されている場合、nextAssignedValue関数はそのempty値を返します。