Iterators
Iteration is the process of stepping through a sequence of values. An iterator is an entity that provides such a sequence of values, one at a time. In SenseTalk, lists, ranges and property lists can all be used as iterators. In addition, you can create your own custom iterator objects. There are several ways that you can step through each of the values supplied by an iterator and work with each one in turn.
Iterating Using Repeat With Each
When you need to work with each value supplied by an iterator and perform some actions with that value, use a repeat with each loop. For example, here's a loop that uses each value from a list:
repeat with each color in ["red", "orange", "yellow", "green"]
put "I think " & color & " is a very pretty color."
end repeat
To iterate over all of the values in a range, use repeat with each just as you would with a list:
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
Iterating Using Each Expressions
When you need to process each value (or a selected set of values) from a list or other iterator to produce a list of result values, an each expression can be very effective. The result of an each expression is always a list. See Each Expressions.
Iterating Using NextValue
The nextValue
function will return the next sequential value from an iterator. When the sequence is exhausted and no more values are available from that iterator, the special constant value end
is returned. The currentValue
function will return the current value again without advancing the iteration.
put 100 to 200 by 50 into range
put range's nextValue --> 100
put range's nextValue --> 150
put range's currentValue --> 150
put range's nextValue --> 200
put range's nextValue --> ⓔ ⓝ ⓓ
Modifying Iteration Using CurrentIndex
Both lists and ranges have a currentIndex
property. When a list or range is first created, the value of the currentIndex
is zero. It is also reset to zero at the start of a repeat with each loop or an each expression for that iterator.
During iteration, the currentIndex
property is incremented each time a value is retrieved from the iterator. The iterator's currentIndex
can be accessed to determine the item number of the current value in the source list or range.
set foo to [a,b,c,d,e]
put foo's currentIndex & each item of foo --> [1a,2b,3c,4d,5e]
What's more, the currentIndex
can be changed to alter the course of iteration. For example, this repeat loop will only display the odd numbers in the range:
set myRange to 1..100
repeat with each item of myRange
put it
add 1 to myRange.currentIndex -- skips the next item
end repeat
Changing a List During Iteration
While iterating over the items in a list using repeat with each
, an each
expression, or the nextValue
function, SenseTalk uses the currentIndex
property of the list to keep track of the iteration. Whenever items are inserted, deleted, or replaced in a list, the currentIndex
property is adjusted appropriately to reflect the change. So it is possible to add, remove, or replace items in a list during iteration without causing problems.
Custom Iterators
Lists and ranges are the most common sources used as iterators. You can also create a custom iterator by making a script or object with a nextValue
handler that returns the next value in a sequence. The values returned can be anything, and the implementation may be as simple as returning the nextValue
from a list that is a property of the object, or may be the next result calculated from some internal state maintained by the object. Iterators of this type are sometimes called "generators" because they generate values one at a time as needed.
To be used as an iterator, there are a few rules an object must follow. First, it must have an objectType
property with a value of "iterator". This tells SenseTalk to call the object's nextValue
handler to obtain each value for a repeat with each loop or an each expression. Second, of course, it must have a nextValue
handler to supply each value in the sequence. The nextValue
handler should return the value end
when the end of the sequence has been reached and no more values are available. If it doesn't do this, a repeat loop should include an exit repeat statement to break out of the loop at some point or it will continue forever. An each
expression should not be used with an iterator that doesn't eventually return end
.
In addition to a nextValue
handler, a custom iterator may optionally also have a startIteration
handler. If present, this handler will be called before nextValue
is called by either a repeat with each
loop or an each
expression to allow the iterator to set up any initial conditions, or to reset its state following any earlier iteration.
Here is an example of a custom iterator that generates values from the Fibonacci sequence, in which each value is the sum of the two preceding values (note that although the Fibonacci sequence is infinite, this iterator only returns the values up to ten thousand, allowing it to be used with an each expression):
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]
Property Lists as Iterators
You also can use a property list as an iterator. When iterating in a repeat loop, the property list supplies each key and its value as a list of two items. An object that defines its own iteration (objects with an objectType of "iterator") use their own iteration; objects with a different objectType iterate over the [key,value] pairs for their properties.
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
When iterating over the properties of a property list outside of a repeat loop, nextKey
can be used to retrieve the next key, nextValue
to retrieve the next value, or nextKeyValue
to retrieve the next [key,value] pair. CurrentKey
, currentValue
, and currentKeyValue
can be used to obtain the corresponding value for the current property without advancing the iteration.
set people to {Marty:12, June:16, Aaron:6, Grace:11}
put people's nextKey --> "Aaron" (iteration starts with the first key alphabetically)
put people's nextValue --> "11" (advances to next key and gets its value)
put people's currentKey --> "Grace" (gets the current key without advancing)
put people's nextKeyValue --> [June, 16] (advances and returns the key and its value)