guides:objects

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
guides:objects [2025-02-25 19:55] – [Breaking the Rules] Fit CoolerObject example in 30 lines geekguides:objects [2025-03-31 17:28] (current) – Move classes header to separate page geek
Line 326: Line 326:
 </runner> </runner>
  
-===== Fundamentals of Class Objects =====+===== Classes =====
  
-==== Function Objects ====+See: [[guides:objects:classes|]]
  
-In AutoHotkey v1, there were several global collections of names that were kept separate. There was a collection of command names, a collection of label names, a collection of function names, and a collection of variable names. AutoHotkey v2 has mostly merged these collections, folding them all into the //just// variable name collection. Label-based subroutines have been replaced in favor of functions. Commands have been replaced in favor of functions. And, critically, functions have been redesigned to all be stored //inside// global variables. 
- 
-Allowing functions to be saved inside variables and passed around like data is known as having [[https://en.wikipedia.org/wiki/First-class_function|first-class functions]]. In AutoHotkey, it is achieved by using [[https://www.autohotkey.com/docs/v2/misc/Functor.htm|function objects]], which are objects that can run code when you use the call syntax: ''name()''. Both user-defined functions and built-in functions are implemented this way, with function definition syntax creating a global variable by the function's name to hold the function object. 
- 
-<runner ahk2> 
-MyFunction() { 
-    ; No matter how this function is called, the message box 
-    ; will say "You called MyFunction". 
-    MsgBox "You called " A_ThisFunc 
-} 
- 
-MsgBox IsObject(MsgBox) ", " Type(MsgBox) 
-MsgBox IsObject(MyFunction) ", " Type(MyFunction) 
- 
-MyVar := MyFunction ; Put MyFunction into a different variable 
-MyVar() ; Call the function object stored inside MyVar 
-</runner> 
- 
-Function objects come inside global read-only variables by default, but can be passed around just like any other object. As shown above, it's easy to put the function object into a different variable even if the new variable has a different name. Additionally, AutoHotkey allows you to //skip// defining the global read-only variable by defining some functions directly inside an expression: 
- 
-<runner ahk2> 
-MyVar := () => MsgBox("You called '" A_ThisFunc "' (the arrow function)") 
-MyVar() 
- 
-; In AHKv2.1 this is allowed as well: 
-;MyVar2 := () { 
-;    MsgBox "You called '" A_ThisFunc "' (the function expression)" 
-;} 
-;MyVar2() 
-</runner> 
- 
-By itself, this syntax is usually seen when defining OnEvent type callbacks. It allows you to skip defining a function that might only be called in one place: 
- 
-<code autohotkey> 
-CloseCallback() { 
-    MsgBox "You tried to close the GUI" 
-} 
-g := Gui() 
-g.OnEvent("Close", CloseCallback) 
- 
-; Can be rewritten as: 
- 
-g := Gui() 
-g.OnEvent("Close", () => MsgBox("You tried to close the GUI")) 
- 
-; Or in v2.1: 
- 
-g := Gui() 
-g.OnEvent("Close", () { 
-    MsgBox "You tried to close the GUI" 
-}) 
-</code> 
- 
-However, where things start to get really interesting is when you put function objects into //other objects//. Just like a function object can be stored inside a regular variable and then that variable becomes callable, a function object can be stored as an object property and then that property becomes callable. A callable property on an object is called a //method//. 
- 
-When you call a function stored as an object property, AutoHotkey does a little trick with the parameter list. If you have ''MyObject'' with a property ''FunctionProperty'' that contains a function object, calling ''MyObject.FunctionProperty(1, 2, 3)'' will automatically translate into (roughly) ''Temp := MyObject.FunctionProperty'' then ''Temp(someObject, 1, 2, 3)'' where ''Temp(...)'' is a regular call to the function. You see, the object that contains the property is passed as a first parameter to the function. 
- 
-<runner ahk2> 
-MyFunction(this, a, b, c) { 
-  MsgBox 'a: ' a '`nb: ' b '`nc: ' c  
-} 
- 
-MyObject := { 
-  FunctionProperty: MyFunction 
-} 
- 
-; These following 4 lines are all equivalent 
-MyObject.FunctionProperty(1, 2, 3) 
-Temp := MyObject.FunctionProperty, Temp(MyObject, 1, 2, 3) 
-(MyObject.FunctionProperty)(MyObject, 1, 2, 3) 
-MyObject.FunctionProperty.Call(MyObject, 1, 2, 3) 
-</runner> 
- 
-==== Prototyping ==== 
- 
-AutoHotkey objects are *prototype* based, but AutoHotkey's docs don't really do a proper job of explaining what that means or how it works. Prototype-based Object-Oriented-Programming (OOP) is a way of arranging objects containing function objects so that the emergent behavior is similar to non-prototype OOP languages (think C++ or Java). 
- 
-The first part of this arrangement was //function objects// being nested inside regular objects. The second part is //prototyping//. Prototyping is the generic term for allowing one object to borrow the properties of another object (the //prototype// object). In AutoHotkey, this is achieved using the "base" mechanism. By adding a base to your object, whenever you try to access a property on your object that does not exist AutoHotkey will then check the base object to see if it exists there instead. 
-<runner ahk2> 
-baseObject := { 
-    someProperty: "alpha" 
-} 
- 
-testObject := { 
-    base: baseObject 
-} 
- 
-MsgBox testObject.someProperty ; Will show "alpha" 
-</runner> 
- 
-==== Class Syntax ==== 
- 
-AutoHotkey's ''class'' syntax is so-called [[https://en.wikipedia.org/wiki/Syntactic_sugar|sugar syntax]]. Sugar syntax is an easier to read and write shorthand for code that is too verbose to work with directly. The implication of calling class syntax as sugar syntax is that you can do almost everything that the ''class'' keyword does entirely without using it. There are a few minor exceptions that we will go over later. 
- 
-Class syntax is used to simultaneously define two things: a "prototype object" and a "class object". A prototype object is used the *base* object for class instances. When you create an object like ''myObject := MyClass()'', the value of ''myObject'' ends up looking something like ''myObject := {base: MyClass.Prototype}''. The prototype object is the object that holds all the method functions that you can call on the class instance. 
- 
-Remembering the fundamental of how functions stored in objects are called, it would mean that in this following example, when ''testMethod'' is called the value of ''this'' will be equal to ''myObject'' //not// ''MyClass.Prototype''. 
- 
-<runner ahk2> 
-testMethod(this, a, b, c) { 
-    MsgBox "this Ptr: " ObjPtr(this) 
-    MsgBox 'a: ' a '`nb: ' b '`nc: ' c 
-} 
-MyClass := { 
-    Prototype: { 
-       functionProperty: testMethod 
-    } 
-} 
- 
-myObject := {base: MyClass.Prototype} 
- 
-MsgBox "Prototype Ptr: " ObjPtr(MyClass.Prototype) 
-MsgBox "myObject Ptr: " ObjPtr(myObject) 
- 
-myObject.functionProperty(1, 2, 3) 
-</runner> 
- 
-The "class object" created by class syntax starts pretty simple: an object with a ''Prototype'' field. But then AHK adds onto that with an "instance factory". Instance factory is a term that I don't think the AHK docs ever uses, but it really should because that's what it would be called in any sane language. 
- 
-An instance factory is a function that creates instances of a class. An instance factory for an AHK class works something like this: 
- 
-<code autohotkey> 
-classFactory(someClass) { 
-    instance := {base: someClass.Prototype} 
-    instance.__Init() 
-    if HasMethod(instance, "__New") { 
-        instance.__New() 
-    } 
-    return instance 
-} 
-</code> 
-The instance factory gets put onto the class object as its "Call" method. With the class factory put onto the class object like this, you can create instances by calling the class object directly: 
-<runner ahk2> 
-testMethod(this, a, b, c) { 
-    MsgBox 'a: ' a '`nb: ' b '`nc: ' c 
-} 
-classFactory(someClass) { 
-    instance := {base: someClass.Prototype} 
-    instance.__Init() 
-    if HasMethod(instance, "__New") { 
-        instance.__New() 
-    } 
-    return instance 
-} 
-MyClass := { 
-    Prototype: { 
-       functionProperty: testMethod 
-    }, 
-    Call: classFactory 
-} 
-myInstance := MyClass() 
-myInstance.functionProperty("alpha", "bravo", "charlie") 
-</runner> 
- 
-This code invokes "Call" automatically, like ''(MyClass.Call)(MyClass)'', which invokes ''classFactory'' and returns the instance object. 
- 
-That's the vast majority of what class syntax does. In that last example, we manually created this class:  
- 
-<runner ahk2> 
-class MyClass { 
-  functionProperty(a, b, c) { 
-    MsgBox 'a: ' a '`nb: ' b '`nc: ' c 
-  } 
-} 
-myInstance := MyClass() 
-myInstance.functionProperty("alpha", "bravo", "charlie") 
-</runner> 
- 
-When defining a class, it allows you to specify static and non-static properties. You can do exactly the same with the manually written code. Static properties get added to the class object. Non-static properties get added to the instance by the ''%%__Init%%'' method called by the instance factory: 
- 
-<runner ahk2> 
-class MyClass1 { 
-    static someProp := 123 
-    someProp := 456 
-} 
-myInstance1 := MyClass1() 
-MsgBox "Static " MyClass1.someProp " | Non-Static: " myInstance1.someProp 
- 
-; Equivalent to 
- 
-classFactory(someClass) { 
-    instance := {base: someClass.Prototype} 
-    instance.__Init() 
-    if HasMethod(instance, "__New") { 
-        instance.__New() 
-    } 
-    return instance 
-} 
-MyClass2 := { 
-    Prototype: { 
-       __Init: (this) => (this.someProp := 456) 
-    }, 
-    Call: classFactory, 
-    someProp: 123 
-} 
-myInstance2 := MyClass2() 
-MsgBox "Static " MyClass2.someProp " | Non-Static: " myInstance2.someProp 
-</runner> 
- 
-=== Unique behavior === 
- 
-As mentioned previously, there are a few unique features of the ''class'' syntax that are not easily replicated. 
- 
-The first is //definition hoisting//. Definition hoisting is the ability to define a class (or other construct) //below// the point where it will be referenced. This allows you to write a class definition at the bottom of your script, but still use it in the auto-execution section. Function definitions are also hoisted in this way. 
- 
-The second difference is that the variable defined using ''class'' syntax to hold the class object is made read-only. If you define a class manually like any other object, that class name can be overwritten later. But if you define it with ''class'' syntax, trying to overwrite the global variable that holds the class object will result in the exception "This Class cannot be used as an output variable."