[v2] Custom for-loops: Enumerators, Iterators, Generators
Posted: 29 Mar 2021, 14:43
This is a guide that explains how to create a custom for loop.
Normally we use for loops without thinking about what goes on behind the scenes. In this case we have an array, and we're iterating over the array. But the for-loop is actually calling list._Enum(). So if you replace the line for letter in list with for letter in list.__Enum() it does the same thing.
So how do we create an enumerator object? Well here's a simple function that generates 42 over and over. Note that &var must be passed as a VarRef.
First, the for loop tries to call constant.__Enum(). It fails and assumes that our function is already an enumerator object, which is what we want. Then, it evaluates var as 42, and sets the letter i to var. Therefore i is now 42.
The for-loop then determines whether it should finish or keep going. This is determined by the return value of the enumerator object. Since the last line is var:=42, and 42 evaluates as True, the loop will continue forever.
How to write enumerators by dividing them into 3 sections.
Let's write a counting function.
The yield statement tells the for loop what the current value is. In this case, the first run yields 0, because start is set to 0. The yield statement is the only time that the VarRef n is set.
The do block is where things happen. In this case we are incrementing start. Why not increment n directly? It wouldn't work because then the function would only return 1 continuously by adding 1 to start.
The last line of the function should always be a boolean. It can be a condition that needs to be evaluated.
This counting function prints out 2, 3, 4.
The constants start and stop are determined by the user to be 2 and 5. It will start at 2 and stop before it reaches 5.
The yield statement yields start as the first value.
The do block increments start by 1.
The continue? expression evaluates whether n is currently less than 5. If it is, it will return false, ending the for-loop.
Enumerate the characters of a string.
The constant pos is the current position in the string. Strings in AHK start from 1 and end at the string length.
The yield statement yields the first letter of the string.
The do block increments the position by 1.
The continue? expression evaluates whether the position has reached the end of the string.
In this example, the do block and continue could be combined as pos++ <= StrLen(s).
Hopefully now enumerators won't be a mystery anymore, and you will be able to write your own.
Normally we use for loops without thinking about what goes on behind the scenes. In this case we have an array, and we're iterating over the array. But the for-loop is actually calling list._Enum(). So if you replace the line for letter in list with for letter in list.__Enum() it does the same thing.
Code: Select all
list := ['A', 'B', 'C']
for letter in list
MsgBox letter
Code: Select all
for i in constant
MsgBox i
constant(&var) => (var := 42)
The for-loop then determines whether it should finish or keep going. This is determined by the return value of the enumerator object. Since the last line is var:=42, and 42 evaluates as True, the loop will continue forever.
How to write enumerators by dividing them into 3 sections.
Let's write a counting function.
single line
fat arrow syntax
Code: Select all
nat() {
; Constants
start := 0
enumerate(&n) {
n := start ; yield statement
start += 1 ; do block
return True ; continue?
}
return enumerate
}
for n in nat()
MsgBox n
The do block is where things happen. In this case we are incrementing start. Why not increment n directly? It wouldn't work because then the function would only return 1 continuously by adding 1 to start.
The last line of the function should always be a boolean. It can be a condition that needs to be evaluated.
This counting function prints out 2, 3, 4.
single line
fat arrow syntax
Code: Select all
range(start, stop) {
; Constants are start and stop.
enumerate(&n) {
n := start ; yield statement
start += 1 ; do block
return n < stop ; continue?
}
return enumerate
}
for n in range(2, 5)
MsgBox n
The yield statement yields start as the first value.
The do block increments start by 1.
The continue? expression evaluates whether n is currently less than 5. If it is, it will return false, ending the for-loop.
Enumerate the characters of a string.
single line
fat arrow syntax
Code: Select all
str(s) {
pos := 1
enumerate(&char) {
char := SubStr(s, pos, 1) ; yield statement
pos++ ; do block
return pos-1 <= StrLen(s) ; continue?
}
return enumerate
}
for char in str("kangaroo")
MsgBox char
The yield statement yields the first letter of the string.
The do block increments the position by 1.
The continue? expression evaluates whether the position has reached the end of the string.
In this example, the do block and continue could be combined as pos++ <= StrLen(s).
Hopefully now enumerators won't be a mystery anymore, and you will be able to write your own.