Classes in AHK, a Dissection (Advanced)
Posted: 30 Jan 2015, 16:54
This "tutorial"/explanation requires the knowledge of how objects (aka arrays) work in AutoHotkey. If I got something wrong, please point it out.
What is a class?
Understanding function references
Lets start off with the concept of functions in AutoHotkey. Originally in AutoHotkey, functions were accessible only by using their name. However, Lexikos got his hands on this and added a brand new way of accessing functions. This way, called Function References meant that you can save a (representation of a) function to a variable, and use that variable in place of the function name. To get a function reference, the new (actually pretty old now) MyReference := Func("MyFunctionName") syntax must be used. To call a function using a reference, RetVal := MyReference.Call(Params...) or RetVal := %MyReference%(Params...) must be used, though the latter is preferred since it also supports function names in addition to references.
A nice "side-effect" (see: intentional) of this is that not only can function references be stored in variables, but they can also be stored in objects. Suddenly, you can collect all your related functions into a single object, MyObj := {Key: Func("MyMsgBox")} and MyObj.Fish := Func("GoFishing"). After storing your function references in an object, you can call them using MyObj.Key.Call("Hello World!") and MyObj.Fish.Call().
There are other ways of calling function references (especially from objects), and one of these is MyObj.Key(Params...) (I removed .Call]). This passes in the object containing the function reference as the first parameter, shifting all the others over. Now your function (if set up with the correct number of parameters) knows in what context it is being called from, allowing it to access other things in the object. If you had the code MyObj := {MyMethod: Func("MyFunction"), Apples: 5}, MyFunction could take in its first parameter as the variable this and do MsgBox, % "Johnny has " this.Apples " apples".
In this code MyObj is what is known in the documentation as a prototype object or base object. Base/Prototype objects are what "class" instances are created from. Creating a base object like this can get unwieldy with size, but luckily there is a better way. The following is effectively the equivalent of previous example:
There are a few key differences in the second example that are important to notice. MyObj is automatically created and made into a super-global variable regardless of where in the code it's defined. Also, the first parameter of the method (this) is added behind the scenes so you don't have to define it explicitly for every method. Finally, MyMethod never exists as a separate function MyFunction.
Understanding class instances (and the new keyword).
An instance of a class is just another object, with its base attribute set to the prototype (base) object. For example, you can create an instance with the code Instance := {base: MyClass} (though this doesn't call the instantiation routines). When the "base" attribute is set to something, AutoHotkey knows to pull information from there if it doesn't already exist in the instance. This applies to calling methods and retrieving attribute values, but not to setting attribute values.
The keyword new is used to easily create an instance of a class/base object, as well as automatically instantiate it. I'm not completely sure how it works internally, but here's to the best of my knowledge. It starts off by creating a new object as in the previous paragraph. Then it calls the (reserved by AutoHotkey, do not define it yourself) __Init() meta-function on it which handles things like non-static class variables. Once it finishes with that it checks for and calls the user defined __New initiation meta-function. If it exists and there is a return value, that is what is returned by new. If it doesn't exist or there isn't a return value, new returns the new instance.
Here is some code demonstrating the __New meta-function:
Understanding inheritance
When AutoHotkey goes to find a attribute in your object, it first does the meta-function object.base.__Get check, then it moves on to "Does attribute exist in the object?". If it does, it uses it. If not it checks "Does it exist in the object's base?". If it does, it uses it. If not, it checks the base's base, and then the base's base's base, and so on until there are no more base objects.
When AutoHotkey goes to set a attribute in your object, it just creates the attribute in the original object. Consider closely what the following code will do:
On line one, it creates an instance of y (think x := {base: y}). On line two, it tries to get x.Attribute. Seeing that x doesn't have a Attribute, and that x.base does, it returns x.base.Attribute (5) instead. On line three, it changes y.Attribute to 3. x still doesn't have a Attribute, and y is still the base of x, so on line 4 it sees when it sees that x doesn't have a Attribute it returns x.base.Attribute (aka y.Attribute, with the value of 3) instead. On line 5 it pulls the value of x.Attribute, but sees that it doesn't have a Attribute and instead uses x.base.Attribute, then it subtracts 1 from that and gives x a Attribute of 2. Since x now has a Attribute, any further changes to y.Attribute are not inherited, and the value 2 will be shown on line 7.
How meta-functions work
Two sections ago I explained the use of the new keyword, and by extension the __New meta-function. A meta-function is a class method that doesn't get explicitly called, but gets called when something happens to the object. Meta-functions are called from the base object not the instance. If you create an object {__Call: Func("MyCallA"), base: {__Call: Func("MyCallB")}}, MyCallB would be used as the meta-function. Note that inheritance applies to meta-functions, so when object.base.Meta doesn't exist (or returns nothing), it checks for object.base.base.Meta, then object.base.base.base.Meta and so on.
There are several meta-functions, and they're listed below along with the context in which they are called:
--- More coming ---
See post source bbcode and revisions here:
In the case of AutHotkey, most commonly an object. In this post, we will be talking about the syntax of making custom object classes that have methods, and how it works.wiktionary wrote:class /klɑːs/
n. A group, collection, category or set sharing characteristics or attributes.
Lets start off with the concept of functions in AutoHotkey. Originally in AutoHotkey, functions were accessible only by using their name. However, Lexikos got his hands on this and added a brand new way of accessing functions. This way, called Function References meant that you can save a (representation of a) function to a variable, and use that variable in place of the function name. To get a function reference, the new (actually pretty old now) MyReference := Func("MyFunctionName") syntax must be used. To call a function using a reference, RetVal := MyReference.Call(Params...) or RetVal := %MyReference%(Params...) must be used, though the latter is preferred since it also supports function names in addition to references.
Code: Select all
MyReference := Func("MyMsgBox")
%MyReference%("Hello World!")
MyMsgBox(MyString)
{
MsgBox, % MyString
}
Code: Select all
MyObj := {Key: Func("MyMsgBox")}
MyObj.Fish := Func("GoFishing")
Tmp := MyObj.Key, %Tmp%("Brb, Going fishin'")
; or MyObj.Key.Call("Brb, going fishin'")
MyObj.Fish.Call()
GoFishing()
{
; Lock the screen, we're going fishing
DllCall("LockWorkStation")
}
MyMsgBox(MyString)
{
MsgBox, % MyString
}
Code: Select all
MyObj := {MyMethod: Func("MyFunction"), Apples: 5}
MyObj.MyMethod("Johnny", 123)
MyFunction(this, Param1, Param2)
{
MsgBox, % Param1 " has " this.Apples " apples for " Param2 " customers"
}
Code: Select all
MyObj.MyMethod("Johnny", 123)
class MyObj
{
static Apples := 5
MyMethod(Param1, Param2)
{
MsgBox, % Param1 " has " this.Apples " apples for " Param2 " customers"
}
}
An instance of a class is just another object, with its base attribute set to the prototype (base) object. For example, you can create an instance with the code Instance := {base: MyClass} (though this doesn't call the instantiation routines). When the "base" attribute is set to something, AutoHotkey knows to pull information from there if it doesn't already exist in the instance. This applies to calling methods and retrieving attribute values, but not to setting attribute values.
The keyword new is used to easily create an instance of a class/base object, as well as automatically instantiate it. I'm not completely sure how it works internally, but here's to the best of my knowledge. It starts off by creating a new object as in the previous paragraph. Then it calls the (reserved by AutoHotkey, do not define it yourself) __Init() meta-function on it which handles things like non-static class variables. Once it finishes with that it checks for and calls the user defined __New initiation meta-function. If it exists and there is a return value, that is what is returned by new. If it doesn't exist or there isn't a return value, new returns the new instance.
Here is some code demonstrating the __New meta-function:
Code: Select all
MyInstance := new MyClass("MyDefault")
MyInstance.MyMethod()
class MyClass
{
__New(Default)
{
InputBox, OutputVar,, Enter a value for your new attribute,,,,,,,, %Default%
this.Attribute := OutputVar
}
MyMethod()
{
MsgBox, % this.Attribute
}
}
When AutoHotkey goes to find a attribute in your object, it first does the meta-function object.base.__Get check, then it moves on to "Does attribute exist in the object?". If it does, it uses it. If not it checks "Does it exist in the object's base?". If it does, it uses it. If not, it checks the base's base, and then the base's base's base, and so on until there are no more base objects.
When AutoHotkey goes to set a attribute in your object, it just creates the attribute in the original object. Consider closely what the following code will do:
Code: Select all
x := new y
MsgBox, % x.Attribute
y.Attribute := 3
MsgBox, % x.Attribute
x.Attribute -= 1
y.Attribute := 7
MsgBox, % x.Attribute
class Y
{
static Attribute := 5
}
Two sections ago I explained the use of the new keyword, and by extension the __New meta-function. A meta-function is a class method that doesn't get explicitly called, but gets called when something happens to the object. Meta-functions are called from the base object not the instance. If you create an object {__Call: Func("MyCallA"), base: {__Call: Func("MyCallB")}}, MyCallB would be used as the meta-function. Note that inheritance applies to meta-functions, so when object.base.Meta doesn't exist (or returns nothing), it checks for object.base.base.Meta, then object.base.base.base.Meta and so on.
There are several meta-functions, and they're listed below along with the context in which they are called:
- __New(Params...) - This gets called as MyObject.base.__New.(MyObject, "Banana", "Sandwich") when an an instance is created using new BaseObject("Banana", "Sandwich").
- __Delete() - This gets called as MyObject.base.__Delete.(MyObject) when the instance gets deleted, such as at the end of the script/function or when you do this to the last reference: MyInstance := AnythingElse
- __Get(Key) - This gets called as MyObject.base.__Get.(MyObject, "Pizza") when something does Var := MyObject.Pizza or Var := MyObject["Pizza"]
- __Set(Key, Value) - This gets called as MyObject.base.__Set.(MyObject, "Pizza", "Cheez") when something does MyObject.Pizza := "Cheez" or MyObject["Pizza"] := "Cheez"
- __Call(Name, Params...) - This gets called as MyObject.base.__Set.(MyObject, "DoSomething", "P1", "P2", "P3") when something does MyObject.DoSomething("P1", "P2", "P3"). Note that you can pass an arbitrary number of parameters to it. You can handle this using Variadic Functions, but that is outside the scope of this explanation.
- __Init() - This is reserved by AutoHotkey and should never be defined.
--- More coming ---
See post source bbcode and revisions here:
Spoiler