#イテレータ
イテレーションは、値のシーケンスを逐次ステップするプロセスです。 _イテレータ_は、一度に1つの値を提供するエンティティです。SenseTalkでは、リスト、範囲、およびプロパティリストのすべてをイテレータとして使用することができます。さらに、独自のカスタムイテレータオブジェクトを作成することもできます。イテレータが提供する各値をステップしたり、順番に各値を操作する方法はいくつかあります。
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-- Puts A1, A2, A3, B1, B2, ...
end repeat
end repeat
Each式を使用した反復処理
リストや他のイテレータから各値(または選択された一連の値)を処理し、結果値のリストを生成する必要がある場合、each式は非常に効果的です。each式の結果は常にリストです。Each Expressionsを参照してください。
NextValueを使用した反復処理
nextValue()関数は、イテレータから次の順序的な値を返します。シーケンスが尽き、そのイテレータからこれ以上の値が 利用できない場合、特殊な定数値endが返されます。
put 100 to 200 by 50 into range
put range's nextValue -- Puts 100
put range's nextValue -- Puts 150
put range's currentValue --> 150
put range's nextValue -- Puts 200
put range's nextValue -- Puts ⓔ ⓝ ⓓ
CurrentIndexを使用した反復処理の修正
反復処理中、currentIndexプロパティはイテレータから値が取得されるたびに増分されます。イテレータのcurrentIndexにアクセスして、ソースリストまたは範囲内の現在の値のアイテム番号を決定できます。
リストと範囲の両方にはcurrentIndexプロパティがあります。リストや範囲が最初に作成されたとき、currentIndexの値はゼロです。また、そのイテレータのrepeat with eachループまたはeach式の開始時にもゼロにリセットされます。
set foo to [a,b,c,d,e]
put foo's currentIndex & each item of foo -- Puts [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 -- skips the next item
end repeat
イテレーション中にリストを変更する
リストの項目をrepeat with each、each式、またはnextValue関数を使用して反復処理する際、SenseTalkはリストのcurrentIndexプロパティを使 用して反復処理を追跡します。項目がリストに挿入、削除、または置換されるたびに、currentIndexプロパティは適切に調整されて変更を反映します。したがって、反復処理中にリスト内の項目を追加、削除、または置換することが可能であり、問題を引き起こすことはありません。
カスタムイテレータ
リストと範囲はイテレータとして使用される最も一般的なソースです。また、順序の次の値を返すnextValueハンドラを持つスクリプトやオブジェクトを作成することにより、カスタムイテレータを作成することもできます。返される値は何でもよく、実装はオブジェクトのプロパティであるリストからnextValueを返すだけのものであったり、オブジェクトによって維持される内部状態から次の結果を計算するものであったりすることができます。このタイプのイテレータは、必要に応じて一つずつ値を生成するため、「ジェネレータ」と呼ばれることがあります。
イテレータとして使用するためには、オブジェクトが守るべきいくつかのルールがあります。まず、それは"iterator"のobjectTypeを持つ必要があります。これはSenseTalkに、repeat with eachループまたはeach式の各値を取得するためにオブジェクトのnextValueハンドラを呼び出すように指示します。もちろん、二つ目は、シーケンスの各値を供給するnextValueハンドラを持つ必要があります。nextValueハンドラは、シーケンスの終了に達し、これ以上利用可能な値がないときに値endを返すべきです。これを 行わない場合、repeatループにはいずれかの点でループを抜け出すためのexit repeatステートメントを含めるべきです、さもなければそれは永遠に続くでしょう。各式は、最終的にendを返さないイテレータと一緒に使用すべきではありません。
カスタムイテレータは、任意でstartIterationハンドラも持つことができます。存在する場合、このハンドラはrepeat with eachループまたはeach式によってnextValueが呼び出される前に呼び出され、イテレータが初期条件を設定したり、以前の反復処理後に状態をリセットしたりすることを可能にします。
以下は、フィボナッチ数列から値を生成するカスタムイテレータの例です。各値は前の2つの値の合計になります(フィボナッチ数列は無限ですが、このイテレータは10,000までの値のみ返します。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]
プロパティリストをイテレータとして使用する
プロパティリストもイテレータとして使用できます。repeatループで反復処理を行うと、プロパティリストは各キーとその値を2つの項目のリストとして提供します。自己の反復を定義するオブジェクト("iterator"のobjectTypeを持つオブジェクト)は、自身の反復を使用し、異なるobjectTypeを持つオブジェクトは、そのプロパティの(キー、値)ペアを反復処理します。
set people to {Marty:12, June:16, Aaron:6, Grace:11}
repeat with each (name,age) in people
put !"[[name]] is [[age]] years old"
end repeat
repeatループの外でプロパティリストのプロパティを反復処理する場合、nextKeyは次のキーを取得するために、nextValueは次の値を取得するために、またはnextKeyValueは次の(key,value)ペアを取得するために使用できます。CurrentKey、currentValue、currentKeyValueは、反復処理を進めずに現在のプロパティの対応する値を取得するために使用できます。
set people to (Marty:12, June:16, Aaron:6, Grace:11)
put people's nextKey -- "Aaron"
put people's nextValue -- "11"
put people's currentKey -- "Grace"
put people's nextKeyValue -- (June, 16)
パラメータとしてイテレータを渡す
イテレータは、別のスクリプトやハンドラにパラメータとして渡すことができます。イテレータは、その現在の状態が保持されたまま渡されます。これには、リストや範囲のcurrentIndex
プロパティの値や、カスタムイテレータのプロパティなどが含まれます。したがって、ローカルスクリプトはイテレータから一度に1つの値を使用してきた場合(nextValue
を使用して)、呼び出されたスクリプトはシーケンスの後の値を使用し続けることができます。
SenseTalkは通常、任意のパラメータ値のコピーを作成するため、呼び出されたスクリプトはイテレータのコピーを受け取ります。イテレータを共有して、ローカルスクリプトと呼び出されたスクリプトの両方がイテレータからの単一の値シーケンスを使用するためには、イテレータを参照で渡します。例えば:
set takeANumber to 1..20
put takeANumber's nextValue-- 1
put takeANumber's nextValue -- 2
scriptThatTakesTwoNumbers @takeANumber -- takes 3 and 4
put takeANumber's nextValue -- 5
参照を渡すための "@" がなければ、最終的なtakeANumber's nextValueの呼び出しは5ではなく3を返します。ここで参照を渡すことにより、両方のスクリプトが呼び出されたスクリプトにコピーを分岐する代わりに同じ値のシーケンスを共有します。
is an iterator
演算子は、値(受け取ったパラメータなど)が反復可能かどうかをテストするために使用できます:
if param(1) is not an iterator then throw "An Iterator Is Required"
反復の再開
範囲やリストの反復を最初の値から再開するには、currentIndexをゼロに設定します。次にnextValueにアクセスすると、範囲またはリストの最初の値が返されます。
set the currentIndex of sequence to zero -- reset iteration
一部のカスタムイテレータは、反復を再開するメカニズムを提供するかもしれませんし、提供しないかもしれません。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 --Puts [a,b,,,,,g,,,,,,,,,,,,,,,,,,,z]
この例のリストには26項目がありますが、そのうち4つだけに値が割り当てられています。他の項目は空です。このリストの項目を反復すると、26項目すべてにアクセスします。場合によっては、明示的に値が割り当てられたリスト項目だけにアクセスできると便利なことがあります。nextAssignedValue関数がこれを実現します。この関数はnextValue関数のように動作しますが、currentIndexプロパティを1だけ増分してそのインデックスの値を返すのではなく、currentIndexをリスト内の次の割り当てられた値がある項目に進め、その値を返します。したがって、上記の例を続けると:
put list's nextAssignedValue --Puts a
put list's currentIndex --Puts 1
put list's nextAssignedValue -- Puts b
put list's currentIndex -- Puts 2
put list's nextAssignedValue -- Puts g
put list's currentIndex -- Puts 7
put list's nextAssignedValue -- Puts z
put list's currentIndex -- Puts 26
put list's nextAssignedValue -- Puts ⓔ ⓝ ⓓ
nextValue
関数とnextAssignedValue
関数の両方がリストのcurrentIndex
プロパティを進めるため、これらの関数への呼び出しを混在させることで、必要に応じてリストの次の連続した値(割り当てられているかどうか)または現在のインデックスの次のリストの割り当てられた値を取得することができます。
空の値は未割り当ての値とは異なります。空の値がリストの項目に格納されている場合、その空の値はnextAssignedValue
関数によって返されます。