An enumerator object is the object that defines the parameters of a for-loop. The default enumerator object gives the contents of an array sorted alphanumerically by the keys. Enumerator objects can be much more flexible than this, generating any values you could want.
An enumerator object is a class that has a method Class.Next. This method gets called every iteration of the for loop. There are up to two parameters for the Class.Next method, not counting the built in "this" parameter. The parameters are most commonly ByRef k and ByRef v. The names of the variables doesn't matter so much, but they do have to be ByRef.
The Class.Next method sets the values of the two ByRef parameters, and they become the two variables k and v from for k, v in MyArray. The method returns a True/False value that indicates if the loop should keep looping or if it should break.
For example, this custom enumerator would enumerate through the values 1, 2, 2, 4, 3, 6 and so on.
Code: Select all
class CustomEnum
{
static i := 0
Next(ByRef k, ByRef v)
{
this.i++
k := this.i
v := this.i*2
return True
}
}
An enumerator object is gotten when a for loop calls the _NewEnum method on the object passed into it. For example, the code for k, v in Members would call Members._NewEnum() to get the enumerator object.
To implement your own enumerator object, you will have to override the _NewEnum method on the object so it returns your custom enumerator. This can be done in a few ways, but the simplest is to just set it to a function reference. For example:
Code: Select all
MyArray := ["Nobody", "expects", "the"]
MyArray._NewEnum := Func("MyNewEnum")
MyNewEnum(this)
{
return new CustomEnum()
}
Code: Select all
MyObject := new ObjectWithCustomEnum()
class ObjectWithCustomEnum
{
_NewEnum()
{
return new CustomEnum()
}
}
For cleaner code, the enumerator object can be defined as a nested class of the original object.
Code: Select all
class ObjectWithCustomEnum
{
_NewEnum()
{
return new this.CustomEnum()
}
class CustomEnum
{
static i := 0
Next(ByRef k, ByRef v)
{
this.i++
k := this.i
v := this.i*2
return True
}
}
}
Code: Select all
class ObjectWithCustomEnum
{
_NewEnum()
{
; Reset i since we aren't making a new object
; that starts off with a fresh i as 0
this.i := 0
return this
}
Next(ByRef k, ByRef v)
{
this.i++
k := this.i
v := this.i*2
return True
}
}
Putting the parts together, you can get a working custom enumerator setup going. Here's an example that enumerates the Fibonacci sequence.
Code: Select all
for a, b in new Fibonacci()
MsgBox, % a ", " b ", " b/a ; b/a is an approximation of the golden ratio
class Fibonacci
{
_NewEnum()
{
return new this.MyEnum()
}
class MyEnum
{
static Fib := [0, 1]
Next(ByRef a, ByRef b)
{
this.Fib.Push(this.Fib[1] + this.Fib[2])
a := this.Fib.RemoveAt(1)
b := this.Fib[1]
return True
}
}
}
Since classes are really just objects, your custom enumerator class can be created using object syntax, for example {Next: Func("CustomNext"), i: 0, Jiggly: "puff"}. Going along with how methods are implemented in AutoHotkey, CustomNext would need to have the "this" parameter explicitly defined. CustomNext(this, ByRef a, ByRef b).
The second ByRef parameter of Next can be made optional, in the case that you want to only use the first value in the for loop (i.e. for k in Thing). This would create a Next method that looked something like this: Next(ByRef a, ByRef b="")
If you're inclined to, you can actually fit this entire system into a single function. This is a function that can be used to let you use a for loop on COM collections.
Spoiler