Classes in AHK, a Dissection (Advanced)

Helpful script writing tricks and HowTo's
geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Classes in AHK, a Dissection (Advanced)

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?

wiktionary wrote:class /klɑːs/
n. A group, collection, category or set sharing characteristics or attributes.
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.

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.

Code: Select all

MyReference := Func("MyMsgBox")
%MyReference%("Hello World!")

MyMsgBox(MyString)
{
	MsgBox, % MyString
}
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().

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
}
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".

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"
}
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:

Code: Select all

MyObj.MyMethod("Johnny", 123)

class MyObj
{
	static Apples := 5
	
	MyMethod(Param1, Param2)
	{
		MsgBox, % Param1 " has " this.Apples " apples for " Param2 " customers"
	}
}
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:

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
	}
}

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:

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
}
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:
  • __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
Last edited by geek on 09 Aug 2015, 22:02, edited 13 times in total.
geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

30 Jan 2015, 16:54

Reserved
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

31 Jan 2015, 17:30

GeekDude wrote:

Code: Select all

MyObj := {MyMethod: Func("MyFunction"), Property: 5}
MyObj.MyMethod("MonkeyTacos", 123)

MyFunction(this, Param1, Param2)
{
	MsgBox, % this.Property ", " Param1 ", " Param2
}
Congratulations, this qualifies as a class with a single method! Now, this might feel like a bit of a strange way to make a class.
Afaik, that is not a "class" in AHK terms. Its an object. In AHK, a "class" is simply an object that is defined with the class syntax sugar, which allows a more common/popular way of working with objects.
GeekDude wrote:Thankfully, there's a prettier way! This is equivalent to the above code block:

Code: Select all

MyObj.MyMethod("MonkeyTacos", 123)

class MyObj
{
	static Property := 5
	
	MyMethod(Param1, Param2)
	{
		MsgBox, % this.Property ", " Param1 ", " Param2
	}
}
As you can see in your usage above, the "class" MyObj is already an "object". In most other languages (class-based OOP), you cannot use a class until you instantiate it (usually with a new keyword). In AHK (a prototype-based OOP), you can use a class without needing to instantiate it, because using the class keyword is actually creating an object, which can then be be used as a prototype or base for other objects.

geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

31 Jan 2015, 17:39

The documentation refers to it as a prototype/base object, and says:
The documentation wrote:For convenience and familiarity, the "class" keyword can be used to construct a base object
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

31 Jan 2015, 18:15

Indeed, thats exactly what I said. I was just pointing out that the reverse (which you stated) is not true: an object is not necessary a class in AHK

geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

31 Jan 2015, 18:23

"class" is a convention, not an actual thing (at least, behind the scenes). I suppose I should point this out and change my terminology slightly
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

31 Jan 2015, 18:36

yes exactly, right here:
GeekDude wrote:

Code: Select all

MyObj := {MyMethod: Func("MyFunction"), Property: 5}
MyObj.MyMethod("MonkeyTacos", 123)

MyFunction(this, Param1, Param2)
{
	MsgBox, % this.Property ", " Param1 ", " Param2
}
Congratulations, this qualifies as a class with a single method!

geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

01 Feb 2015, 12:51

Updated, check the changelog on the gist to see what has changed
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

01 Feb 2015, 23:35

I hope I'm not being a pain hehe. I think your tutorial is very good and valuable and I just want to help. Perhaps Lexikos should chime in and clarify on this stuff
GeekDude wrote:
What is a class?


In AutoHotkey, a "class" is an object containing function references.
I don't think that this is true. Afaik in AHK a "class" is simply an object that is defined with the class syntax, rather than other syntaxes. It doesn't have to contain any function references.
Last edited by guest3456 on 01 Feb 2015, 23:39, edited 1 time in total.

geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

01 Feb 2015, 23:39

I can see the argument being made for the __Class property being what makes an object a class, and the argument that containing function references makes an object a class, or even having the __Init (reserved) meta-function making an object a class. However, using the sugar syntax instead of writing your base property from scratch is an argument I find difficult to understand.
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

01 Feb 2015, 23:50

GeekDude wrote:I can see the argument being made for the __Class property being what makes an object a class
I suppose. But you could manually assign a __Class key yourself.

Code: Select all

thing := {}
thing.foo := "bar"
thing.__Class := "classclass"
thing.test := Func("thing_test")
thing.test()

thing_test(this) {
   MsgBox % this.foo . "`n" . this.__Class
}
You're saying the thing object above is a "class" since it has both a __Class property as well as a function reference?
GeekDude wrote: and the argument that containing function references makes an object a class,
Where are you getting this idea from? But I guess it explains why you wrote the original thing that I posted about, where your object had a method linked to a function reference, and you called it a "class"
GeekDude wrote: However, using the sugar syntax instead of writing your base property from scratch is an argument I find difficult to understand.
Well the docs pretty much say it:
http://ahkscript.org/docs/Objects.htm#Custom_Classes wrote: Classes

For convenience and familiarity, the "class" keyword can be used to construct a base object. A basic class definition might look like this..
Not to mention the fact that the class syntax was added way after the introduction of objects into AHK_L for the reasons described in the quote above. There were long discussions on autohotkey.com forum about the syntax. But I could be wrong thats why prob best to get Lex's input

geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

02 Feb 2015, 00:01

guest3456 wrote:Where are you getting this idea from?
Because that's one of the major differences between traditional object usage and "class" usage.
guest3456 wrote:Not to mention the fact that the class syntax was added way after the introduction of objects into AHK_L for the reasons described in the quote above. There were long discussions on autohotkey.com forum about the syntax
"Classes" and function references were added together in the same update. Before "classes" I suppose you still could've made an object {__Class: "name"}, but you couldn't have created an object with function references in it. That's one of the reasons I find the existence of function reference in an object to be a compelling definition, seeing as the class keyword is nothing special (you can do the things it does without actually using it).

If the class keyword is what makes a class, then the term "class" should (in my mind) only be used when referring to the definition, and base object or prototype object should be used in all other cases. However, that gives the question about what class instances should be called, just normal objects? Objects that are created using the new keyword? Objects that are created using the new keyword from base objects that were created using the class keyword?
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

02 Feb 2015, 00:42

GeekDude wrote:
guest3456 wrote:Where are you getting this idea from?
Because that's one of the major differences between traditional object usage and "class" usage.
Says who?

GeekDude wrote: "Classes" and function references were added together in the same update. Before "classes" I suppose you still could've made an object {__Class: "name"}, but you couldn't have created an object with function references in it.
Are you sure you aren't confused between function references and object methods?

If Func references didn't exist, then yes obviously you couldn't add them to an object. The Func object was added later.

But even before Func refs were introduced, you could add a method to an object very easily:

Code: Select all

thing := {}                 ;// create object
thing.foo := "bar"           ;// define a property
thing.test := "thing_test"   ;// define a method without using Func ref
thing.test(thing)              ;// call the method

thing_test(this) {
   MsgBox % this.foo 
}
GeekDude wrote: That's one of the reasons I find the existence of function reference in an object to be a compelling definition, seeing as the class keyword is nothing special (you can do the things it does without actually using it).
Indeed the class keyword ISN'T anything special, which is what I've been getting at.
GeekDude wrote: If the class keyword is what makes a class, then the term "class" should (in my mind) only be used when referring to the definition, and base object or prototype object should be used in all other cases. However, that gives the question about what class instances should be called, just normal objects? Objects that are created using the new keyword? Objects that are created using the new keyword from base objects that were created using the class keyword?
This confusion might stem from the difference between class-based OOP and prototype-based OOP.

The term 'base object' or 'prototype object' should only be used in we are talking about the relationship between a parent object and a child object, or an object that derives or inherits from another. It would be rare to use these terms, but I guess you could use 'base object' as the object created with the class syntax and all derived objects as those created with new.

Afaik, in AHK, there are normal variables, which can be assigned a singular value such as a string or an integer. And then there are objects, which are associative arrays which hold multiple key:value pairs. Thats it. The [] array syntax allows you an easier and more convenient way to create an object with only integer keys. The class syntax allows you an easier way to create an object with properties and methods all defined inside one block. Using the class keyword creates an object that is usable. Using the new keyword you can create another instance of that object. I guess you could also say its another instance of the "class" if you want..

lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: Classes in AHK, a dissection

02 Feb 2015, 04:24

"Class" in a programming context is just a concept stemming from the plain old word:
class
klɑːs
noun
1. a set or category of things having some property or attribute in common and differentiated from others by kind, type, or quality.
It doesn't matter whether you use the "syntax sugar" to define a class of objects. If you define behaviour or structure for a common set of objects, you have a class.
guest3456 wrote:In most other languages (class-based OOP), you cannot use a class until you instantiate it (usually with a new keyword).
Yes you can. You can use static methods, properties and data. In some languages which retain meta-data about the code after compilation (such as .NET based languages), you can essentially get a reference to a class, and use it to instantiate objects or access static members.
guest3456 wrote:yes exactly, right here:
Didn't you say that's not a class? Did you change your mind? ;)
Well the docs pretty much say it:
The docs do not define what a class is, or what can be considered a class and what cannot. Those kinds of considerations, like this discussion, don't have much practical value in my opinion.
GeekDude wrote:"Classes" and function references were added together in the same update.
FYI, a "function reference" is just a pointer to a Func object. Func objects have existed ever since functions and function-calls were added to AutoHotkey, since AutoHotkey is a scripting language and each element of the language is generally represented by an object (in the C++ sense). They just weren't always accessible to scripts in that way.
However, that gives the question about what class instances should be called, just normal objects?
All objects are instances of a class. They all have properties and attributes in common.
guest3456 wrote:aik, in AHK, there are normal variables, which can be assigned a singular value such as a string or an integer. And then there are objects, which are associative arrays which hold multiple key:value pairs. Thats it.
Normal variables can be assigned objects, too, and array elements can be assigned singular values such as a string or integer. I don't see what your purpose is in comparing a variable to an associative array. That all objects have some attributes in common (i.e. they are associative arrays) shows that they are all instances of a broad class; it does not mean that they are not also instances of more specific classes.

For objects, there's also File, Func, Enumerator, Property, RegExMatchObject, ComObject, ComEnum and ComArrayEnum. These objects are not associative arrays.
geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

02 Feb 2015, 08:34

guest3456 wrote:Are you sure you aren't confused between function references and object methods?
But even before Func refs were introduced, you could add a method to an object very easily:
I wasn't aware, and that is a good thing to know. Thanks for telling me!
guest3456 wrote:Indeed the class keyword ISN'T anything special, which is what I've been getting at.
By saying "Afaik in AHK a "class" is simply an object that is defined with the class syntax", which would mean that class would have some intrinsic uniqueness? I got the opposite impression of what you meant by what you said.
lexikos wrote:It doesn't matter whether you use the "syntax sugar" to define a class of objects. If you define behaviour or structure for a common set of objects, you have a class.
That's a pretty broad statement, but I like it.
lexikos wrote:
Well the docs pretty much say it:
The docs do not define what a class is, or what can be considered a class and what cannot. Those kinds of considerations, like this discussion, don't have much practical value in my opinion.
I'm not saying your point of view is a bad one, but I think it's important to be well informed about what terminology should be used to make sure you're getting your point across correctly. I also think this is especially important when you're writing a guide for other people to learn from and use as reference.
lexikos wrote:FYI, a "function reference" is just a pointer to a Func object. Func objects have existed ever since functions and function-calls were added to AutoHotkey, since AutoHotkey is a scripting language and each element of the language is generally represented by an object (in the C++ sense). They just weren't always accessible to scripts in that way.
Cool, I wasn't sure if functions were stored the same way before you could reference them or not, now I know.

Wow, forums pack a lot of information into tiny posts
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

02 Feb 2015, 12:37

lexikos wrote:
guest3456 wrote:yes exactly, right here:
Didn't you say that's not a class? Did you change your mind? ;)
No. :) He said he should change his terminology, I agreed, and said, "yes change this right here:"
lexikos wrote:
Well the docs pretty much say it:
The docs do not define what a class is, or what can be considered a class and what cannot. Those kinds of considerations, like this discussion, don't have much practical value in my opinion.
I pretty much agree with that, calling something a 'class' or 'object' seems trivial. Just, given the history of AHK and the order in which things were added, it just seemed more appropriate what I was saying before. To call everything an 'object' and then call objects created with the class keyword "classes"
GeekDude wrote:
guest3456 wrote:Indeed the class keyword ISN'T anything special, which is what I've been getting at.
By saying "Afaik in AHK a "class" is simply an object that is defined with the class syntax", which would mean that class would have some intrinsic uniqueness? I got the opposite impression of what you meant by what you said.
Yeah this whole thing is only semantics, but I do agree that its important especially when trying to write an explanation for others. The only uniqueness that I meant is that a class is created with the class syntax. Thats just my opinion I suppose

lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: Classes in AHK, a dissection

02 Feb 2015, 14:04

GeekDude wrote:That's a pretty broad statement, but I like it.
"Class" has a pretty broad meaning, despite what guest3456 thinks.
bobc119
Posts: 23
Joined: 10 Jan 2014, 17:02

Re: Classes in AHK, a dissection

02 Feb 2015, 14:09

Thank you GeekDude! This has cleared up a lot of questions I had. I've avoided learning about classes because they are overwhelming, like learning a whole new way to program, and you've made them seem pretty simple.

One question I have that you didn't mention about inheritance: Does the same thing apply to methods as you described for properties?

If I define a method() in the base class, and then define the same method() in a class that extends the base class, does it always use the new non-base version?
geek
Posts: 1051
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Classes in AHK, a dissection

02 Feb 2015, 14:15

methods and properties follow the same rules, since methods are just properties with function references as the values. As for extending objects, a extends b makes a new object a with b as the base object. Therefore, due to the rules of inheritance, it'd use the ones defined in a before using the ones defined in b.
guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: Classes in AHK, a dissection

02 Feb 2015, 15:13

lexikos wrote:
GeekDude wrote:That's a pretty broad statement, but I like it.
"Class" has a pretty broad meaning, despite what guest3456 thinks.
lol I don't dispute that statement. I think my point was pretty clear. My post that you quoted, which had GeekDude's object with a single method, would you call that object a "class", in AHK terms? If you want to use object/class interchangeably thats fine. Is it just semantics? In a way yes. But there are things to be aware of when using the class keyword in AHK which are different than in other class-based OOP languages. You yourself warned about this stuff years back in the thread linked and elsewhere on the other forum, and were initially opposed to the class syntax for that very reason. Anyway I think this has been beaten to death


Return to “Tutorials (v1)”

Who is online

Users browsing this forum: No registered users and 41 guests