object classes: redefine __Set() temporarily / general queries

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

object classes: redefine __Set() temporarily / general queries

11 Jan 2018, 09:31

- I'm preparing a tutorial on object classes. I have enough content for a tutorial already, but I would like to clarify certain things.
- In this example I redefine the __Set() meta-function temporarily. It successfully changes the __Set() meta-function and then restores the original __Set() meta-function. However, two approaches that attempt to restore the original __Set() meta-function don't work, in case anyone can shine some light on this.

Code: Select all

;redefine __Set() temporarily
;the assignment of 'a' and 'c' should occur as normal, the assignment of 'b' should be blocked
oArray := {}
oArray.1 := "a"
oArray.base := {"__Set":"MyFuncPreventSet"}
oArray.2 := "b"
oArray.base.Delete("__Set") ;works
;oArray.Delete("base") ;doesn't work
;oArray.base := {} ;doesn't work
oArray.3 := "c"
MsgBox, % oArray.1 "_" oArray.2 "_" oArray.3

MyFuncPreventSet()
{
	MsgBox, % A_ThisFunc
	return
}
Last edited by jeeswg on 17 Jan 2018, 20:46, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 10:50

Hello :wave: .

Code: Select all

oArray.Delete("base")
base is a property, which sets or gets the base object, that is, obj.base:= ... doesn't store a key named base in obj, hence, oArray.haskey("base") is false.

Code: Select all

oArray.base := {}
This doesn't change the base because your __set function is invoked (because oArray doesn't have a key named base), and explicitly return, which means the assignment has been handled, but the function doesn't do anything, so nothing happens.

You can use objrawset to store a value associated with a key named base.

See Objects, it is described there.

Cheers.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 11:16

- Hello Helgef, nice to speak to you again, it seems like it's been a while.
- So, for standard AutoHotkey arrays, obj.base refers to a property, not an object.
- But there is such a thing as a base object, is there not, where is that stored, is it a standard AutoHotkey array? Thanks.

==================================================

[EDIT:] I was going to start this as a new thread, but it's relevant here:

Methods? Meta-functions? Both?
__Call/__Get/__Set, __Delete

Methods?
__Init/__New
_NewEnum/Next

Method? Key?
__Class

Object? Property? Both?
base
[EDIT:][.base is a property?]
[EDIT:][a 'base object' is referenced internally?]
[EDIT:][.base is a property that returns information about a 'base object'?]

Methods:
InsertAt / RemoveAt
Push / Pop
Delete
MinIndex / MaxIndex / Length
SetCapacity / GetCapacity
GetAddress
_NewEnum [also listed above]
HasKey
Clone

Deprecated:
Insert
Remove

Also:
Call

Function:
ObjRawSet

Anything missing?

==================================================

SOURCES:

[List of methods.]
Object
https://autohotkey.com/docs/objects/Object.htm

[From an old version of the documentation.]
Arrays of arrays and the [,,] operator. - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 2262#p2262
Meta-functions are methods defined by an object's base which can define exactly how the object should act when an unknown key is requested.
Objects
https://lexikos.github.io/v2/docs/Objects.htm
To run code when the last reference to an object is being released, implement the __Delete meta-function.
[EDIT:][However, this suggests that __Delete is not a meta-function.]
Objects
https://autohotkey.com/docs/Objects.htm#Meta_Functions
Meta-functions define what happens when a key is requested but not found within the target object.
==================================================

Also:
- I could find barely any references to __Init or __Class in the documentation.
- Note: to fully search the documentation, decompile AutoHotkey.chm into separate htm (and other) files, e.g. via 7-Zip or hh.exe, and search, e.g. via NirSoft SearchMyFiles.

- __Class appears in the base object, and contains the name of an object's class, but I suppose that no-one ever uses __Class inside a class definition. Or is this not true?

- Since there is almost no information re. __Init in the documentation, I tried to do some research.
- __Init 'should not be used by the script'.
- __Init appears in the base object, if you define keys within the class body.
- It seems that you cannot both define keys within the class body and have an __Init method.

Code: Select all

;works
class MyClass1
{
	a := "A"
}
obj := new MyClass1
MsgBox, % obj.a

;works, although note: __Init 'should not be used by the script'
class MyClass2
{
	__Init()
	{
		this.b := "B"
	}
}
obj := new MyClass2
MsgBox, % obj.b

/*
;Error: Duplicate declaration.
class MyClass3
{
	a := "A"
	__Init()
	{
	}
}
*/
Demonstrate __Class and __Init listed within .base:

Code: Select all

class MyClass0
{
}

class MyClass1
{
	a := "A"
}

;note: __Init 'should not be used by the script'
class MyClass2
{
	__Init()
	{
		this.b := "B"
	}
}

oArray0 := new MyClass0
vOutput := ""
for vKey, vValue in oArray0.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

oArray1 := new MyClass1
vOutput := ""
for vKey, vValue in oArray1.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

oArray2 := new MyClass2
vOutput := ""
for vKey, vValue in oArray2.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
Last edited by jeeswg on 03 Feb 2018, 08:53, edited 6 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 14:20

Hello jeeswg, nice to speak to you too :).
- But there is such a thing as a base object, is there is not, where is that stored, is it a standard AutoHotkey array?
Any object can be a base for another object. Each object is aware of its base (if it has one), it has a reference stored internally. There is also a default base object.
__class
Is automatically added to objects which are created with the class keyword, and is assigned the name which follows it. In effect, it does class x { static __class := "x" } for you when you do class x {}. You can add a __class key/value pair to an array, then, passing it to type() (in v2) will return "Class".
__init
Similarily, __init is automatically added and called when you use the new operator on the object. It moves the (non-static) class body assignments into the method, and excecutes them before __new is called. In v1,

Code: Select all

class c{
	a:=1
}
yields

Code: Select all

class c{
	__init(){
		this.a:=1
	}
}
but in v2, it yields

Code: Select all

class c{
	__init(){
		objrawset(this,"a",(1))	
	}
}
which is much better and it gives you something to ponder for your converter :thumbup:.

This explains why you get duplicate declaration error, and you should be able to guess what happens if you make an explicit assignement to __class in the class body.

Cheers.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 17:23

==================================================

BASE OBJECT

- Do you know where that base object reference is stored?
Is it something similar to this:

Code: Select all

;reference count
NumGet(&oObj + A_PtrSize)
;key count
NumGet(&oObj + 4*A_PtrSize)
- Can anything interesting be done with the default base object? E.g. to alter the blueprint for each new standard AutoHotkey array that is created.
- Some simple tests:

Code: Select all

MsgBox, % IsObject("") ;0
MsgBox, % IsObject("".base) ;1

oObj := "".base
MsgBox, % &oObj
MsgBox, % NumGet(&oObj + 4*A_PtrSize) ;0 ;get key count (do not rely this working in all AHK versions)

vOutput := ""
for vKey, vValue in "".base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
==================================================

TESTS WITH __CLASS

- Indeed you get a 'Duplicate declaration' error if you try to define a key called __Class.
- I'm curious as to whether the use of 'static' makes any difference.

Code: Select all

;Error: Duplicate declaration.
class MyClass1
{
	__Class := "hello"
}

oArray := new MyClass1

;Error: Duplicate declaration.
class MyClass2
{
	static __Class := "hello"
}

oArray := new MyClass2
- Also, Type, in AHK v2, returns information as you described.

Code: Select all

;for AHK v2
oArray := {}
MsgBox(Type(oArray)) ;Object
oArray.__Class := "hello"
MsgBox(Type(oArray)) ;Class

oArray := new MyClass
class MyClass
{
}

MsgBox(Type(oArray)) ;MyClass
MsgBox(Type(oArray.base)) ;Class
==================================================

STATIC IN CLASS BODY ASSIGNMENTS

- What is the distinction between static/non-static class body assignments?
- Here is an interesting example based on an example under 'Understanding inheritance', from here:
Classes in AHK, a Dissection (Advanced) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=6177
- I was going to start this as a new thread, but it's relevant here.

Code: Select all

obj := new MyClass
MsgBox, % obj.a " " obj.b ;5 5
MyClass.a := 3, MyClass.b := 3
MsgBox, % obj.a " " obj.b ;3 5
obj.a -= 1, obj.b -= 1
MyClass.a := 7, MyClass.b := 7
MsgBox, % obj.a " " obj.b ;2 4

class MyClass
{
	static a := 5
	b := 5
}
- The post makes some interesting claims, I don't know if they're clearly stated in the documentation.
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.
==================================================

TUTORIALS / CONVERSION TIPS ETC

- Interesting information re. AHK v1/v2 differences.

- I wonder what single-digit number of people know/understand some of this stuff.
- This is why I write tutorials, advocate for certain newbie-friendly improvements in my Wish List 2.0, and do summaries of what conversion to AHK v2 involves.
Wish List 2.0 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 13&t=36789
AHK v1 to AHK v2 conversion tips/changes summary - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 37&t=36787
- Maybe some of this stuff seems obvious to the people that know it, but I have made a lot of effort trying to understand it, and it's not yet obvious to me. Well thanks to this thread, I've almost got a coherent tutorial ready.
- If you have any good links that would be great.

- You could do a short/long summary of conversion issues relating to objects/object classes. You'd be one of the best people to have a go at this. E.g. start a new thread, it doesn't have to be perfect, or very extensive, and you could add to it periodically.
- Potentially, you could do a tutorial on object classes. This is quite challenging, and you could wait till I do one to make it easier. But, if interested, you'd be good at this.

- I would approach both like this:
- start collecting some random ideas to include
- pad it out a bit/sort it a bit
- submit the post
- make edits

- Another point of interest would be how much the Type custom backport differs from the AHK v2 function:
commands as functions (AHK v2 functions for AHK v1) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 37&t=29689
- It should at least be possible to add support for returning 'Class', and the name of a custom class.
- I don't mind if it can't recreate everything, but it would be good to make any simple improvements.
- I would be interested to know if backporting some functionality would actually be impossible.
- [EDIT:] The AHK v2 documentation has an example 'Object_Type' function, with similar functionality to the modification that I considered making to the 'AHK v2 functions for AHK v1' custom 'Type' function backport, it then says this:
Type
https://lexikos.github.io/v2/docs/commands/Type.htm
However, unlike this function, the Type function does not cause the object's meta-functions to be called.
So perhaps this is the problem, and it might be better to leave the current 'Type' backport as it is, unless someone has any further information to add.

==================================================
Last edited by jeeswg on 17 Jan 2018, 06:09, edited 2 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 17:37

- Do you know where that base object reference is stored?
No I do not ;)

Code: Select all

a:={base:["hello_base"]}
p:=NumGet(&a + 2*A_PtrSize)
msgbox object(p).1
msgbox object(&a.base).1
- What is the distinction between static/non-static class body assignments?
It is described in the help file.
Classes in AHK, a Dissection (Advanced)
- The post makes some interesting claims,
I didn't read GeekDude's tutorial (yet?), I will not comment (yet?).
This is why I write tutorials, advocate for certain newbie-friendly improvements in my Wish List 2.0, and do summaries of what conversion to AHK v2 involves.
:thumbup:

Cheers.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 17:41

LINKS

A collection of links for my intended tutorial.

links (documentation):
Objects
https://autohotkey.com/docs/Objects.htm
Objects
https://autohotkey.com/docs/Objects.htm#Custom_Classes
Object
https://autohotkey.com/docs/objects/Object.htm

links (from the Tutorials section):
Classes in AHK, a Dissection (Advanced) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=6177
How to create custom enumerator objects - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=7199
Classes in AHK, Basic tutorial. - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=6033
Make subclasses act like methods - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=38149
[Tutorial/trick] "Encapsulating" so-called private methods/p - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=7310
jeeswg's objects tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=29232
Beginners OOP with AHK - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=41332
OOP design patterns in AHK - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=38151
TIP: Singleton with Autohotkey - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=24917

links (further):
Code Puzzle Thread - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 17&t=25683
objects/object classes: new features from a newbie-friendly perspective - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=42135
Class-based programming - Wikipedia
https://en.wikipedia.org/wiki/Class-based_programming

links (meta discussion):
Class definition syntax for AutoHotkey - Offtopic - AutoHotkey Community
https://autohotkey.com/board/topic/6547 ... utohotkey/
Keyword Prototype instead of Class - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=3641

links (array from class, versus modify a standard array):
[create a standard AHK array, then change the class (not recommended)]
Classes in AHK, a Dissection (Advanced) - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 441#p55441
Classes in AHK, a Dissection (Advanced) - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 477#p55477

links (_NewEnum/Next):
[to add]
[basically any thread that contains both '_NewEnum(' and 'Next(']
[Coco did many examples, other contributors: GeekDude, Helgef, HotKeyIt]
[after I've resolved many basic custom class issues, I intend to look at _NewEnum/Next]
[I didn't really find any 'standard' _NewEnum/Next examples, I've seen various approaches]
[EDIT:] Listed here:
objects: enumerator object queries - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 71#p198571
Last edited by jeeswg on 28 Jul 2018, 13:29, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 18:32

- Re. static assignments in the class body.
- Is it possible to reassign a non-static assignment? E.g. redefine 'a' in the example below.
- [EDIT:] Also, is it possible to do 'MyClass ... .a', to retrieve 'A'?

Code: Select all

class MyClass
{
	a := "A"
	static b := "B"
}

MsgBox, % MyClass.a ;(blank)
MsgBox, % MyClass.b ;B

oArray := new MyClass
MsgBox, % oArray.a ;A
MsgBox, % oArray.b ;B
MsgBox, % oArray.base.a ;(blank)
MsgBox, % oArray.base.b ;B

MyClass.a := "AA"
MyClass.b := "BB"

MsgBox, % MyClass.a ;AA
MsgBox, % MyClass.b ;BB

oArray := new MyClass
MsgBox, % oArray.a ;A
MsgBox, % oArray.b ;BB
MsgBox, % oArray.base.a ;AA
MsgBox, % oArray.base.b ;BB

MyClass.b := "BBB"
MsgBox, % oArray.b ;BBB

oArray.b := "BBBB"
MsgBox, % MyClass.b ;BBB

;assignment in an array derived from the blueprint,
;modifies the blueprint
oArray.base.b := "BBBB"
MsgBox, % MyClass.b ;BBBB
- I did find 2 links but they didn't really (or didn't obviously) address what I was interested in.
Objects
https://autohotkey.com/docs/Objects.htm ... lasses_var
Objects
https://autohotkey.com/docs/Objects.htm ... _staticvar

- This quote appears to correspond with what GeekDude was saying in that quote I posted above.
Objects
https://autohotkey.com/docs/Objects.htm ... _staticvar
To assign to a class variable, always specify the class object; for example, ClassName.ClassVar := Value. If an object x is derived from ClassName and x itself does not contain the key "ClassVar", x.ClassVar may also be used to dynamically retrieve the value of ClassName.ClassVar. However, x.ClassVar := y would store the value in x, not in ClassName.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 19:08

- Is it possible to reassign a non-static assignment? E.g. redefine 'a' in the example below.
- [EDIT:] Also, is it possible to do 'MyClass ... .a', to retrieve 'A'?
As I said, the non-static assignments are put in the __init function. You can change the function, eg,

Code: Select all

MyClass.__init := "f"
f(this){
	this.a:="C"
}
Edit, to retrieve,

Code: Select all

MyClass.__init.call(o:=[])
msgbox % o.a
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

11 Jan 2018, 19:40

- Very nice. How do you know these things? By reading the source code? Any links at all?
- I'm not planning to ask any more questions today, so you can get some rest!
- We've (you've) accumulated some really useful fundamental info here. Cheers.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

12 Jan 2018, 01:28

- Here's a class that aims to append 'Z' to any string/numeric value that is assigned to a key.
- In case it can be optimised in any way.
- This seems like a basic thing to do, but the code for __Set() seems a bit unwieldy, and I didn't find any good examples anywhere. I systematically looked through threads containing '__Set(', 80 threads.
- One thing that emerges is an array that may or may not be empty, [{}*], it seems that this can be handled when retrieving a key but not when used with ObjRawSet.

Code: Select all

q::
oArray := new MyClassAppendWhenSet
oArray.a := "a"
oArray["b", "c"] := "bc"
oArray["c", "d", "e"] := "cde"
oArray["x"] := 1
oArray["y"] := {"z":2}
MsgBox, % oArray.a ;aZ
MsgBox, % oArray.b.c ;bcZ
MsgBox, % oArray.c.d.e ;cdeZ
MsgBox, % oArray.x ;1Z
MsgBox, % oArray.y.z ;2

MsgBox, % oArray.a[{}*] ;aZ ;works
;ObjRawSet(oArray[{}*], "d", "d") ;doesn't work
ObjRawSet(oArray, "e", "e")
MsgBox, % oArray.d ;(blank)
MsgBox, % oArray.e ;e
;ObjRawSet(oArray.e[{}*], "f", "f") ;doesn't work
return

class MyClassAppendWhenSet
{
	__Set(oParams*)
	{
		vValue := oParams.Pop()
		vKey := oParams.Pop()
		if !IsObject(vValue)
			vValue .= "Z"
		if !oParams.Length()
			return ObjRawSet(this, vKey, vValue)
		oParams2 := {}
		for vKey2, vValue2 in oParams
		{
			if (A_Index = 1) && !this.HasKey(vValue2)
				ObjRawSet(this, vValue2, {})
			else if !this[oParams2*].HasKey(vValue2)
				ObjRawSet(this[oParams2*], vValue2, {})
			oParams2.Push(vValue2)
		}
		return ObjRawSet(this[oParams2*], vKey, vValue)
	}
}
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

12 Jan 2018, 06:02

An obvious problem: When you do oArray["b", "c"] := "bc" you store oArray["b"], hence, when you do oArray["b", "d"] := "bd", __set is not invoked, and Z is not appended. The trick is to not store keys in the object calling __set (this), instead, you store it in another object, which is stored in this. Example,

Code: Select all

class append {
	__new(l){
		objrawset(this,"__a", l)
		objrawset(this,"__o", [])
	}
	__get(k*){
		return this.__o[k*]
	}
	__set(k,p*){
		v := p.pop() . this.__a
		return this.__o[k,p*] := v
	}
}
extra
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: object classes: redefine __Set() temporarily

12 Jan 2018, 18:34

jeeswg wrote:- Is it possible to reassign a non-static assignment? E.g. redefine 'a' in the example below.
At design time, you can edit the line. After the object is created, you can assign whatever value you like to whatever key you like. If you want to change the initialization code at runtime before you create the object, you should rethink your design. If you need to rely on undocumented details (such as the effect of calling __init), you should rethink your design. Initialization can be performed in a well-defined method, such as __new or one you define and can override.
[EDIT:] Also, is it possible to do 'MyClass ... .a', to retrieve 'A'?
You can also retrieve 'A' by properly instantiating the class, but if you do this (or call __init) just to retrieve the "default" value of the property (which may not be what you get), I would question your design.

If you want a true default value which can be referenced directly from the class but can still be overridden by instances, currently the easiest way is to just add static. (On the other hand, I consider removing automatic inheritance of static variables in v2 as it can cause confusion.)
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

12 Jan 2018, 20:33

- @Helgef: Thanks.
- In your example, is it not possible to replace:

Code: Select all

	__set(k,p*){
		v := p.pop() . this.__a
		return this.__o[k,p*] := v
	}
with:

Code: Select all

	__set(p*){
		v := p.pop() . this.__a
		return this.__o[p*] := v
	}
- And in my MyClassAppendWhenSet example, I believe that replacing {} with new MyClassAppendWhenSet, twice, will work. Or are there some other problems.

Code: Select all

q::
oArray := new MyClassAppendWhenSet
oArray["a", "b", "c"] := "abc"
oArray["a", "b", "d"] := "abd"
MsgBox, % oArray.a.b.c
MsgBox, % oArray.a.b.d
return

class MyClassAppendWhenSet
{
	__Set(oParams*)
	{
		vValue := oParams.Pop()
		Loop, % oParams.Length() ;diagnostic
			vKeyPath .= (A_Index=1?"":" ") oParams[A_Index] ;diagnostic
		MsgBox, % "this " vKeyPath " := " vValue ;diagnostic
		vKey := oParams.Pop()
		if !IsObject(vValue)
			vValue .= "Z"
		if !oParams.Length()
			return ObjRawSet(this, vKey, vValue)
		oParams2 := {}
		for vKey2, vValue2 in oParams
		{
			if (A_Index = 1) && !this.HasKey(vValue2)
				ObjRawSet(this, vValue2, new MyClassAppendWhenSet)
			else if !this[oParams2*].HasKey(vValue2)
				ObjRawSet(this[oParams2*], vValue2, new MyClassAppendWhenSet)
			oParams2.Push(vValue2)
		}
		return ObjRawSet(this[oParams2*], vKey, vValue)
	}
}
- Your approach has the advantage of not using custom arrays for every array, and my approach has the advantage of working pretty much as a normal array does.
- I generally intend to use your approach because it's useful for assigning keys that can't clash with a method, but I was also interested in investigating approaches that write to the keys directly.

- Two other points:
- If we want to turn our (standard AHK/custom) array into a COM object that other scripts/programs can use, I suppose this is the way. Is a standard AHK array by itself not a COM object?
ObjRegisterActive - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=6148
- I mentioned getting the reference count and key count by using NumGet above, all undocumented/not to be relied upon of course. I was curious about what the other offsets are, and how many there are. And more generally, how arrays are stored, where/how are the pointers to keys stored.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

12 Jan 2018, 22:21

- @lexikos: Thanks for your response.
- That's true, some of the approaches I mentioned are undesirable, e.g. instead of using the undocumented __Init, I would use class body assignments and __New.
- The way I see it, is that the more background knowledge you have, the better, even if that includes knowing techniques that you wouldn't normally/ever use.
- AFAIK, roughly speaking, a class is syntax sugar that creates a standard AutoHotkey array with certain keys, and the new operator acts a bit like the Clone() method.
- I think the simplest and most comprehensive way to understand AutoHotkey object classes, and to avoid all kinds of pitfalls is to: understand how what you specify in a class definition, directly affects the resulting 'blueprint' array, and to know the differences between the 'blueprint' array and a newly-created instance of that array.

[two hypothetical cases]
- A benefit of understanding object classes in a fuller way: potentially, if it's the fastest, least memory-intensive, approach, I might consider creating a massive standard AutoHotkey array, and afterwards add one or two methods to it. I.e. it's easy to populate a standard AHK array with multiple keys, but less so, a custom AHK array.
- Potentially, if possible, I might like to change the blueprint for the standard AutoHotkey array, (to affect all new instances of standard AutoHotkey arrays,) for a particular task.

[documentation: reveal behind-the-scenes information in a separate page?]
- In a tutorial/documentation, I would be tempted to outline all of the stuff people would most likely need/want to know, and then have a separate section with behind-the-scenes information.
- Or perhaps I'd combine the two, but point out where things were mentioned more as background information, than as recommended techniques.
- Sometimes, a bit of background information early on, makes things easier. Otherwise people misunderstand things, overcomplicate things, miss simple connections, or attribute things to 'magic'.
- There's a trade-off between not revealing background information because people might use (misuse) undocumented techniques that will change in future, but having an excellent understanding of object classes, versus, hiding some of the fundamentals from people, but them then misusing and misunderstanding the documented information.

[classic questions]
- Ultimately, I'd like to know the answers to questions such as these, so I that I don't depend on anybody else for knowledge of arrays.
- what are the built-in meta-functions/methods/properties (all of them)
- what's the difference between a static/non-static class body assignment
- can you can redefine all aspects of a class after the script starts
- can/should the __Init/__Class methods ever be used (probably no)
- are there other methods that should probably never be used
- [EDIT:] can methods etc be added that affect the default AHK array
- [EDIT:] what happens when AHK arrays are created/destroyed
- [EDIT:] what is stored where in the address space (e.g. to understand the consequences of inserting/deleting keys, and hence how to optimise this for large numbers of keys (e.g. set the capacity, add in alphabetical order, and delete starting from the end)
- also: how will AHK v1/v2 objects differ (one difference: in AHK v2, like other loops, the key/value variables will be cleared when the loop completes)
- (information re. AHK v1/v2 is available here:)
AutoHotkey v2
https://autohotkey.com/v2/

- This was a classic thread re. the benefits of understanding.
- It's only since this week that I would have been able to offer the same answer.
TIP: Singleton with Autohotkey - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=24917
Last edited by jeeswg on 14 Jan 2018, 02:28, edited 2 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

13 Jan 2018, 14:51

- In your example, is it not possible to replace:
Obviously, but it is not important for the example, and not equivalent.
my approach has the advantage of working pretty much as a normal array does.
:crazy: I do not understand :lol:
are there some other problems.
It should be clear, that if you reassign a key*, __set isn't invoked, so you tell me, is that a problem? :)

Cheers
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

14 Jan 2018, 01:51

- What do you mean 'not equivalent', why would someone write 'k1, kother*', instead of 'kall*', there would probably be some reason.
- By 'as a normal array does', my __Set() meta-function assigns to the key you expect it to, not to some obscure object subkey. If you write to the actual keys, and not to some other keys (thereby invoking __Get/__Set every time), wouldn't that reduce overhead. But there are pros and cons of both approaches. Hmm, are there any handy names for the two approaches to make it easier to talk about them.
- Yes, __Set() works only when you assign for the first time (unless the key never exists because you're storing the data in a special object subkey), but maybe I only want a special action to occur only on the first assignment, and not on subsequent assignments, e.g. to force numeric-looking keys to be numeric/string keys, or block certain keys from being created (numeric key names/string key names/reserved keywords). Another use, when using __Get(), to enforce a value of 0, not a blank string.
- ... I had thought about changing the __Set() meta-function for standard AHK arrays, to make them handle numeric-looking key names as AHK v2 does, before I knew that AHK v2 would make that the default behaviour.
- When I asked about my __Set() meta-function you proposed a completely different way of storing the keys. I'm glad you did though, because I knew about that approach, and you answered some of my questions on it.

- Is there a neat way to assign to objects as normal, except for any keys that would clash with method names, and store them somewhere else? Other than by creating a subkey such as oArray._MyData.
- Or more generally, can you protect a key, and prevent it being written to.

- An attempt at investigating the data after &oArray:
- One thing that I'm unsure about is, where pointers to strings are stored in the array structure (you can retrieve them via GetAddress). In general, if I knew that all of the strings in an array would be stored in locations near each other, I could more easily find multiple unused characters, to use as temporary characters by doing a binary search.
- I've been curious about how AHK handles certain string manipulations without the use of delimiter characters.

Code: Select all

;n, description (for UInt data at &oArray+4n)
;0, _
;1, ref count
;2, ? 0
;3, _
;4, key count
;5, capacity
;6, ? numeric key count
;7, ? numeric key count

q:: ;investigate AHK arrays
oArray := {}
oArray.1 := JEE_StrRept(Chr(0xAAAA), 4)
oArray.2 := JEE_StrRept(Chr(0xBBBB), 4)
;oArray.3 := JEE_StrRept(Chr(0xCCCC), 4)
oArray.3 := JEE_StrRept(Chr(0xCCCC), 20)

vOutput := ""
for vKey, vValue in oArray
{
	vNum := oArray.GetAddress(vKey)
	vNum := Format("{:X}", vNum)
	vNum := Format("{:8}", vNum) ;pad with spaces
	vOutput .= vNum "`t" vKey "`r`n"
}

Loop, 40
{
	vOffset := (A_Index-1)*4
	vNum := NumGet(&oArray+vOffset, 0, "UInt")
	vNum := Format("{:X}", vNum)
	vNum := Format("{:8}", vNum) ;pad with spaces
	vOutput .= vNum ","
}

Clipboard := vOutput
return

JEE_StrRept(vText, vNum)
{
	if (vNum <= 0)
		return
	return StrReplace(Format("{:" vNum "}", ""), " ", vText)
	;return StrReplace(Format("{:0" vNum "}", 0), 0, vText)
}
- Re. undocumented features, here's a way to more safely use the undocumented technique to retrieve the key count of an array. The only undocumented AHK feature that I currently use.
- The safety check takes a one-line function and makes it hefty.

Code: Select all

;==================================================

JEE_ObjCount(oObj)
{
	;note: this technique is undocumented, and may not work in future
	return NumGet(&oObj + 4*A_PtrSize)
}

;==================================================

JEE_ObjCountEx(oObj)
{
	static vIsReady
	;this check tries to confirm that the undocumented technique will work
	if !vIsReady
	{
		oArray := {}
		;add a mix of integer and string key names
		Loop, 149 ;123+26 = 149 (arrays are case insensitive)
			oArray[Chr(A_Index)] := ""
		if !(NumGet(&oArray + 4*A_PtrSize) = 123)
		{
			MsgBox, % A_ThisFunc ":`r`n" "error: object count failed"
			Exit
		}
		vIsReady := 1
	}
	;note: this technique is undocumented, and may not work in future
	return NumGet(&oObj + 4*A_PtrSize)
}

;==================================================

JEE_ObjCount2(oObj)
{
	vCount := 0
	for vKey, vValue in oObj
		vCount++
	return vCount
}

;==================================================
- [EDIT:] Great example, just the sort of thing I'd want in my tutorial. Re. the use/omission of 'this', the use of 'base'/'base.base'/'base.base.base' etc, and nested classes.
Class definition syntax for AutoHotkey - Offtopic - AutoHotkey Community
https://autohotkey.com/board/topic/6547 ... ntry417165
- This sort of meta-discussion helps to clarify some of the fundamental concepts.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

15 Jan 2018, 15:47

Hello.
What do you mean 'not equivalent'
I mean just that, and it implies that the proposed function definition changes the behaviour of the method __set.
why would someone write 'k1, kother*', instead of 'kall*'
Surely you know what it means, and the amount of reasons are endless. For the example, I just picked one way to do it, without any (conscious) reasoning.
- By 'as a normal array does', my __Set() meta-function assigns to the key you expect it to
Either one understand what the code does, in which case it does what one expect, or one do not understand what the code does, in which case one's expectations are irrelevant.
maybe I only want a special action to occur only on the first assignment
That is fine. You might be interested in the sub-classing arrays of arrays example, it is not a very good example (imho), but if you fix it, you may learn how to make your class with an alternative approach.

I look forward to see your tutorial, cheers :wave:
Spoiler
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: object classes: redefine __Set() temporarily

15 Jan 2018, 17:11

- OK, by 'not equivalent', I think that maybe you just meant := {} and := new MyClassAppendWhenSet are not equivalent, which, yes, is true.
- And by 'changes the behaviour', I think you mean that we would then change the behaviour of __Set() for all child objects (they would be custom not standard arrays).
- We had a discussion about things being misunderstood on the Internet once before, I believe. With this discussion I want to get every detail right, so I don't mind if you add 2 sentences to every 1 sentence to make things clearer. Also, this will then make follow-up questions less likely.
- By 'assigns to the key you expect it to', I was thinking of what I might call a 'custom array', it would work just like a standard AHK array would, but with subtle differences. This would be something that somebody else could use, and would store keys in exactly the place that they would normally.
- That 'sub-classing arrays of arrays' example is great, it seems to have a neat hack that avoids the more convoluted approach of using 'new' every time. This is exactly the sort of thing you can only do if you go for the deep/fundamental understanding that I'm aiming for in my tutorial. You mention trying to 'fix' it, I'm not really clear what's wrong with it.

==================================================

WRITING TUTORIALS

- Thanks for the encouraging words re. a tutorial.
- I'll give it a go, but I am concerned about making any number of misstatements (which obviously I'm trying to avoid).
- I think two important principles are (1) bottom-up/fundamentals/theoretical, (2) practical. I aim to combine the two.
- Two potential problems in other tutorials that I'm trying to avoid.
- THEORETICAL. Some people can think they're covering 'fundamentals' when they talk about (valid) buzzwords like 'class' and 'prototype' without really explaining anything or giving concrete examples. Thus you explain nothing, and have the false perception of depth.
- PRACTICAL. You start off with some basic classes for window/file information, something that I, myself, intend to do. However, as we've seen in some of the classic links on this page, there are some key details, fundamentals, that really you need to know, or would very much benefit from knowing. The 'right'/best way to do something, is often a hack, a perfectly acceptable, uncontroversial hack. With object classes, you can't get far without fundamentals.

==================================================

SURPRISES AS A NEWCOMER TO CUSTOM CLASSES

- Coming to custom classes without any preconceptions. Three simple things that I thought might be mainstream. An array where you can have methods/keys with the same name. Read/Write methods, that work every time you read or write to a key (whether it exists or doesn't exist). The ability to refer back to parent/ancestor objects whenever you do anything.

- BArray, which is relevant to the methods/keys concern.
obj.HasKey() fails for all keys if a key called HasKey exists - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 15#p148015
Possible AHK bug - the word "base" is missed in an array for-loop - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 27#p113927

==================================================

AHK V2 CHANGES

- I went over the v2-changes/v2-thoughts pages again, after having tried to study the A-Z of objects.
- 2 classic changes: roughly speaking: a key name that looks numeric is stored as a numeric key, for k,v in object: k,v are cleared once the loop ends.
- Is '__Set' being replaced with 'Set' etc. [EDIT:] But wouldn't 'Delete' and '__Delete' clash? [EDIT:] It looks like some methods currently have two possibilities e.g. HasKey and _HasKey, although I didn't find '_HasKey' in the documentation anywhere.
v2-changes
https://autohotkey.com/v2/v2-changes.htm
All built-in methods of Objects and SafeArrays no longer support the _ (underscore) prefix, except _NewEnum, which now requires it (for compatibility with COM objects). ObjNewEnum(x) is still equivalent to x._NewEnum() for Objects.
- This idea is interesting, it touches on something I mentioned earlier.
v2-thoughts
https://autohotkey.com/v2/v2-thoughts.htm
new MyClass[a,b,c] could be reserved as a more convenient way to initialize a specialised array/collection type (new MyClass{a:b} is already "reserved" by way of being treated as a syntax error).
- A way to create a custom WinMove function, and refer to the built-in WinMove function etc would be very useful.
If base is reserved everywhere, it could be used for calling an overridden built-in function (such as base.WinMove(x, y) in a redefinition of WinMove).
- Anyone for Len, that is oArray.Len() and/or oArray.Length(). I thought it would be Len to match StrLen.

==================================================

CUSTOM CLASSES V. FUNCTIONS

[EDIT: Section rewritten.]

- Custom classes, if I were to make more use of them, I would see three areas:
- An array that is convenient as a tool when writing scripts, e.g. a variant of the built-in standard AHK array, e.g. case sensitive or case insensitive arrays, with additional enumerator possibilities: alphabetical order, get key/value pairs in reverse order, or in the order they were added to the array.
- An array for use with other scripts (or programs), so, a COM object.
- Specific, but rare, situations. Perhaps for use in a custom file/registry loop.

- I would generally recommend people to write libraries in a dual object class/stand-alone functions way. You can use the functions as stand-alone functions, without using the class, and the class makes use of the stand-alone functions for most of its functionality.

- Some problems I see where people use an object class library, when a function library would be better.
- Classes add additional difficulty/complexity where it isn't needed, and errors.
- If I write a function that handles an hWnd, that is more universal, if I write a class that only works with a custom window/control object, that is less useful.
- With separate classes for each type of GUI control, I could end up with similar but inconsistent objects. I would especially recommend using stand-alone functions for GUI controls, with a class mostly as a simple wrapper, for anyone who wants it. Generally I would say that GUI functions should stick pretty closely to the window messages that they are probably based on.
- Often there are quirky things in the class implementations that I see. I am more likely to find things I dislike in a class, than in a simple stand-alone function.
- Also, often you get a class that 'tries to do too much', something that simple stand-alone functions are less likely to do. Imagine that the user simply doesn't like your grand scheme, are there simple functions that they can repurpose for their own goals.
- With separate functions you can take out only the bit of the code that you need for some other project.

- Classes are more convoluted than separate functions.
- Functions are easier for translating to/from other projects that may not even use AutoHotkey.
- Let's you say you want to understand how to get from A to B, you have to jump about through various class methods, diagnosing the code. E.g. WinClip, a great script, can be quite hard to follow, and instead of using WinClip, I use separate functions that perform a similar role. Comparing certain simple functions that I have written, with how they are split up across methods in WinClip, WinClip makes some quite straightforward things appear exceedingly complicated.
- It's harder to debug the code in classes, and it's often undesirable to try to add new features.

- Speaking more generally about some of the libraries that I have seen, I think that some people have an unfortunate predilection for, a blind ill-considered ideological bias in favour of, object classes, where they're not wanted or needed.
Last edited by jeeswg on 16 Jan 2018, 15:53, edited 2 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: object classes: redefine __Set() temporarily

16 Jan 2018, 14:34

We had a discussion about things being misunderstood on the Internet once before, I believe. [...] I don't mind if you add 2 sentences to every 1 sentence to make things clearer.
Indeed, communication via text is unnatural, it has practical advantages though ;). I might be a bit vague sometimes, you should interpret that as a sign of respect, I trust you can work out the details yourself, I believe it favours learning. (That doesn't imply any disrespect when I try to be overly clear about something, if that should happen.) Also, it might save me some typing :D .
by 'not equivalent', I think that maybe you just meant
No, I meant that this,

Code: Select all

__set(k,p*){
	v := p.pop() . this.__a
	return this.__o[k,p*] := v
}
isn't equivalent to this,

Code: Select all

__set(p*){
	v := p.pop() . this.__a
	return this.__o[p*] := v
}
And, I still mean it.
And by 'changes the behaviour', I think you mean
No, I mean that the two __set methods above, doesn't behave the same.
k,v are cleared once the loop ends.
More correctly; they are restored to whatever they were before the loop.
Is '__Set' being replaced with 'Set' etc. [...] [EDIT:] It looks like some methods
Indeed, it means that some of the built-in methods which in v1 can also be called by prefixing a single underscore (_), no longer can be called that way in v2.
- That 'sub-classing arrays of arrays' example is great, it seems to have a neat hack that avoids the more convoluted approach of using 'new' every time. You mention trying to 'fix' it, I'm not really clear what's wrong with it.
It uses new as many times as your code. There is no hack, the process is described beneath the code, and complies with the documented behaviour of __set. It works, but is relying on, and therefore encourages use of, an unfit feature of the language, that is the absence of a run-time error when a method is called with too few, or too many, parameters. The first, more severe (imo) problem, is fixed in v2 :thumbup:.
CUSTOM CLASSES V. FUNCTIONS
That is a bit vague, I will try too work out the details myself :)

Cheers.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: dipahk, Nerafius, RandomBoy and 171 guests