====== Making Custom Enumerators (v1 Guide) ======
Originally by [[user:geek]]
===== What is an Enumerator Object =====
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.
===== How do you make an Enumerator Object =====
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.
class CustomEnum
{
static i := 0
Next(ByRef k, ByRef v)
{
this.i++
k := this.i
v := this.i*2
return True
}
}
===== How to implement your custom Enumerator Object=====
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:
MyArray := ["Nobody", "expects", "the"]
MyArray._NewEnum := Func("MyNewEnum")
MyNewEnum(this)
{
return new CustomEnum()
}
Another way would be to create a custom class that already has ''_NewEnum'' overridden.
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.
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
}
}
}
Or if you want, it can even be the same object!
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
}
}
===== Trying it out=====
Putting the parts together, you can get a working custom enumerator setup going. Here's an example that enumerates the Fibonacci sequence.
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
}
}
}
===== Notes, tips and tricks=====
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.
for element in Wrapper(document.getElementsByTagName("div"))
MsgBox, % element.innerHTML
Wrapper(ByRef this, ByRef p="")
{
if IsObject(this) && ComObjType(this)
return {_NewEnum: Func("Wrapper"), self: this}
else if IsObject(this) && ObjHasKey(this, "_NewEnum")
return {Next: Func("Wrapper"), self: this.self, i: 0}
else
return this.i