Objects - preview of upcoming changes

Discuss the future of the AutoHotkey language
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Objects - preview of upcoming changes

13 Oct 2018, 18:55

Current version requires v2.0-a101. Earlier versions still available on GitHub.

Over the last 16 months, I have created a script library to test a number of ideas for potential changes to how objects work. I am fairly happy with the results, and have begun integrating the changes. This library script changes objects to be mostly the way I imagine they will be in the final version.

Object.ahk

Summary of changes implemented by the script:
  • Separate data and interface: array elements and properties/methods are separated, and generic objects do not have array elements by default and do not support obj[n]. Use obj.%n% (v2.0-a101+) to access dynamic properties/methods.
  • Separate methods and properties.
  • Separate static and instance members.
  • New data types: Array (linear), Map (associative, type- and case-sensitive).
  • Meta-functions are replaced with new meta-methods, which work more like normal methods.
  • Different enumeration pattern: _NewEnum and Enumerator objects are replaced with __forin and function/closure objects (or an object with Call instead of Next).
  • Reduced multi-dimensional array support: Using multiple indices does not automatically create arrays of arrays.
  • New exception type hierarchy.
  • New error dialog with stack trace, formatting changes and a few shortcut buttons.
  • New object model:
    • Since static and instance members are separate, each class is split into the class itself (static members) and its prototype.
    • Each object (if under control of the library) ultimately inherits from Object.prototype.
    • Each class which extends Object is actually an instance of Class (i.e. inherits from Class.prototype).
    • Properties and methods can be defined by calling methods on an individual object or a class's prototype.
    • Methods are provided to test for the existence of a property or method.
See GitHub (link above) for code, documentation and details about some parts that haven't been implemented.

Using the script

Read the included documentation before using the script.

For basic use of the script, #include Object.ahk (including only this file should not interfere with existing code).
To enable alterations to Object(), {}, Array() and [], #include Object.Override.ahk.
To enable alterations to non-object values, #include Object.Values.ahk.
To enable the new error dialog, #include ErrorDialog.ahk (either include it in the auto-execute section or call OnError("ErrorDialog")).

To #include any of these files, do one of the following:
  • #Include by full/relative path.
  • Place files directly in a Lib folder and use #include <name> without .ahk.
  • Place files in Lib\Object and use #include <Object\name> without .ahk.
There are a few requirements when using the script that are entirely due to technical limitations, and would not exist if these changes were built-in:
  • When defining a class, make sure it extends Object or a subclass of Object.
  • When defining a class, do not place properties or methods directly inside the class. Instead, place them inside nested classes class _instance or class _static as appropriate. The first time the class object is accessed or instantiated, these nested classes are automatically merged. These nested classes act as a substitute for a static keyword which could hypothetically be applied to any property or method (instance members would be defined by default).
  • To define a property and method with the same name (not recommended), create the property after the object is created (in __new or __initclass).
  • Fixed: Data/array element access should be done through obj.Item[keyOrIndex] instead of obj[keyOrIndex], except with Array, which supports both. Custom collection classes can define an Item property with parameter(s).
  • To enumerate properties with for, use for propname, propvalue in new Enumerator(myObject.Properties()). for cannot accept an enumerator function/closure directly with the current implementation. Follow the same pattern for any other explicitly-called enumeration methods (for instance, if someone defines a method that returns a reverse-order enumerator).
TODO
  • Replace SetCapacity, GetCapacity and GetAddress with a dedicated binary buffer type.
  • Fix Array so that setting Length to a larger value does not cause the new last element to incorrectly be marked as having a value set.
  • Other forgotten things.
Last edited by lexikos on 06 May 2019, 16:43, edited 1 time in total.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Objects

15 Oct 2018, 03:49

Hello. I've only skimmed the documentation, but it looks like very diligent work, thanks for sharing.
Separate data and interface...
New object model
This is very appropriate, great :thumbup:.
Different enumeration pattern:
Excellent.

The error dialog looks great.

I look forward to look into this further and hear what others have to say.

Cheers.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Objects

15 Oct 2018, 07:55

I've looked through this as well. Haven't had time to play with it.

I think some examples of how the new usage would work might be helpful. I still consider myself a novice when it comes to objects and OOP. I know what static and instance members are but I don't understand the difference between array elements and properties. To me, they seemed the same I guess.

Reading through some of the documentation on GitHub, it sounded as if you were thinking that different syntaxes would be used for properties vs. array elements.
Actual implementation: Meta-functions cannot currently differentiate between obj.x and obj["x"]; therefore, object types which contain data are expected to provide a parameterized property obj.Item[key]. Instances of Array also support myArray as the index must be an integer.


It seems like this is a great deal more complex than what we've had and potentially what we need. At least that's my very initial feeling without having a good understanding of how the usage of this would look like. I certainly want to but am just having a hard time wrapping my head around it all...
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Objects

15 Oct 2018, 08:07

I'd really like to see the effects of this. Sadly I currently don't seem to have the time to play around with it.
I'm hesitant to build anything major using this library but it would be the only way to really see the effects.
That being said every change seems to be perfect for me.

@kczx3
static are properties of your class.
They are not available to the instance other than through accessing the class itself.
I like this new addition because it adds a seperation where you were not able to seperate before.
Recommends AHK Studio
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Objects

15 Oct 2018, 08:12

I have put Object.ahk and Object.Errors.ahk in my lib folder and tried running the example found in Errors.md. It doesn't seem like the ErrorDialog is pointing to the right line though. The forum isn't letting me attach a png so here is the MsgBox's text.

---------------------------
Temp.ahk
---------------------------
Error in #include file "C:\Users\cgoepfrich2.MUNSON\Google Drive\AHK\AutoHotkeyV2\Lib\Object.Errors.ahk":
(TypeError) Invalid parameter #1

Specifically: twice (String)

Line#
660: if f := cls.←method["__initclass"]
661: f.call(cls)
662: }
001: {
002: Throw extype.new(p*)
003: }
009: {
---> 010: While (stk := Exception("", -A_Index-1)).What != -A_Index-1
011: if !(stk.File ~= "\\Object(?:\.\w+)?\.ahk")
012: if skip_frames-- <= 0
013: Break
015: ObjRawSet(this, "Message", '(' type(this) ') ' msg)
016: ObjRawSet(this, "Extra", Object_String(extra))
017: ObjRawSet(this, "File", stk.File)
018: ObjRawSet(this, "Line", stk.Line)

The current thread will exit.
---------------------------
OK
---------------------------
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Objects

15 Oct 2018, 08:15

nnnik wrote:
15 Oct 2018, 08:07
@kczx3
static are properties of your class.
They are not available to the instance other than through accessing the class itself.
I like this new addition because it adds a seperation where you were not able to seperate before.
It sounds like you are describing private properties to me. Now that I think about it, I've typically only used the word static with methods in other languages like PHP and JavaScript.
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Objects

15 Oct 2018, 09:40

I don't know whether PHP or JavaSctipt has static attributes.
I only know static from Java - though I know of a few other languages that probably have it.

private is a variable thats inside an object or class and can only be acessed inside the scope of the object or class itself.
static is a variable that only exists within the class rather than any derived instances.
If my Bitmap class has the static method "screenshot" then calling it on the GDI class itself will make the screenshot.
Whereas calling it on a specific Bitmap will result in an error since it doesn't have this method.
Recommends AHK Studio
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Objects

15 Oct 2018, 09:50

Ok, thanks for the explanation!
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: Objects

16 Oct 2018, 03:00

kczx3 wrote:
15 Oct 2018, 07:55
I think some examples of how the new usage would work might be helpful.
Have you looked at the test cases (class Tests)? They're very arbitrary, but any examples I might come up with right now would be much the same.

Reading through some of the documentation on GitHub, it sounded as if you were thinking that different syntaxes would be used for properties vs. array elements.
Yes, I thought I made that clear.
I wrote:Separate syntax for properties vs. array elements
  • Let obj.x and obj.x() access a property or method only.
  • Let obj[index] perform indexing, as defined by the object's class.
  • {a: b, c: d} is most often used to create ad-hoc objects with known properties, so these should be properties.
  • Provide some other way to access properties with calculated names.

It seems like this is a great deal more complex than what we've had and potentially what we need.
What's "this"?

Since you quoted the "Actual implementation" section, I'll point out that this applies specifically to the script and not the hypothetical built-in implementation. Mostly it is due to technical limitations of the current AutoHotkey build.

At least that's my very initial feeling without having a good understanding of how the usage of this would look like.
Mostly the usage would look like it does now, but each element of syntax would be given a more specific purpose. Where before arr[index] and obj.prop could both access an array element or property or return a method reference, in future arr[index] would only ever access an array element and obj.prop would only ever access a property.

The basic philosophy is that breaking the object model into smaller (even if more numerous) pieces makes the individual components simpler, easier to understand and use, and less error-prone.
Last edited by lexikos on 15 Jun 2020, 20:39, edited 1 time in total.
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: Objects

16 Oct 2018, 03:23

kczx3 wrote:
15 Oct 2018, 08:12
I have put Object.ahk and Object.Errors.ahk in my lib folder and tried running the example found in Errors.md. It doesn't seem like the ErrorDialog is pointing to the right line though.
I downloaded a fresh copy of the repository (as a zip), extracted, renamed the "Object.ahk-master" folder to "Lib", copied the ding() example into the parent directory and added #Include <Object> (above or below the example). The error dialog pointed at ding("twice").

I did not use ErrorDialog in this test because the MsgBox text you posted is clearly from the standard dialog. Did you follow the instructions?
To enable the new error dialog, #include ErrorDialog.ahk (either include it in the auto-execute section or call OnError("ErrorDialog")).
ErrorDialog points at the same line as the standard dialog, unless you change the script file after loading it. For either dialog to point at the line you indicated, Exception() must fail to return an appropriate What/File combination. This would happen if you compile the script, since the (debugging) call stack is not available. It would also happen if every stack entry for the current thread is somehow inside an Object.ahk or Object.something.ahk file, such as if you name your main script file that way.

Make sure you have v2.0-a100, as I only test with the latest alpha.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Objects

16 Oct 2018, 08:17

lexikos wrote:Have you looked at the test cases (class Tests)? They're very arbitrary, but any examples I might come up with right now would be much the same.
I did briefly look at that file earlier. I took another look again and frankly, I just think that it is above my head. I struggle with that kind of abstract/arbitrary code. Thanks for pointing me to it though.
lexikos wrote:What's "this"?
I just meant all of the changes outlined in your post. Again, I think that the implementation is just above my head.

Your below comment makes me seem less concerned though.
The basic philosophy is that breaking the object model into smaller (even if more numerous) pieces makes the individual components simpler, easier to understand and use, and less error-prone.
Regarding the new ErrorDialog, it appears the issue was because I was using ahk-v2-a99. Updating to a100 now points to the correct line.
iseahound
Posts: 1427
Joined: 13 Aug 2016, 21:04
Contact:

Re: Objects

27 Oct 2018, 06:10

Quick question: how fast do you expect the pure data objects to be? As fast as associative arrays? c?
iseahound
Posts: 1427
Joined: 13 Aug 2016, 21:04
Contact:

Re: Objects

18 Nov 2018, 23:47

obj.%name% is a fairly obvious extension of %varname% and %funcname%().
Agreed. If obj.x is a property now, and obj["x"] is data access, would you consider obj."x" for data access as well? This is just to mirror the current adaptability between integers array[1] and array.1 as well as JSON-like objects ({x: "100", y: "100"} is a property and now {"x":"100", "y":"100"} is data?)
Problem: Defining an instance variable (a.k.a. property) will override any method which has the same name. The problem may be compounded by the fact that variables and functions do not act this way.
If it is possible to detect where the property or method is being called from, either inside or outside of its class, then calling something with the same name should resolve to a method outside of the class, and resolve to a property if called inside the class. This would be in line with the concept of get and set being internal representations of the object where as call is an external representation, as noted by the analogy get is to call as set is to ???.
Note: obj.x := y cannot be used to define a method at runtime. This could be seen as a loss of convenience; on the other hand, the more explicit mechanism - obj.DefineMethod("x", y) - is clearer, easier to search for, and much less likely to have an unintended effect.
I think this is an intended consequence and is in line with the idea of it being hard by default to define object internals, externally.
_NewEnum is renamed to __forin
I feel like this is a mistake when C# already defines IEnumerable for data and IObservable for streams or asynchronous data.

Overall I think these changes are great. If there is anything I want to add it would be about lazy evaluation. Currently I use the .base keyword to collate collections of data. Let's say that my data changes over time, so I'd make a new object and set the old object as the base of the new object. This allows for lazy evaluation in grabbing the most recent data. I've been thinking of recommending a Flatten() function for a while now, just to make a deep object shallow again.

In addition, many functional languages add laziness into the compiler/interpreter. The trick to dealing with data types efficiently is to only make changes when needed. Let's say I have an array of numbers, and I want to multiply them all by 948713984. If the array is super long, this would delay the next line of code which is to retrieve the value at index 42. With a lazy mindset, this multiplying action could be queued, and retrieving the value at index 42 would perform the multiplying action only at index 42, leading to O(1) vs O(n).
iseahound
Posts: 1427
Joined: 13 Aug 2016, 21:04
Contact:

Re: Objects

19 Nov 2018, 12:38

After reading about Iterators and Observers some more, I've been thinking that if "it is more natural to enumerate an array's contents by default rather than its properties", then wouldn't it be more natural to subscribe to an array's properties? For example, if my object is a window then it has x, y, width, and height properties. When a user moves the window, then the properties are updated, then some action must occur in response to a change in properties (such as redrawing the window).

This seems like a natural consequence of the separation of data and properties. If data is "dead" collection of keys, then properties are an "alive" collection of keys. When processing a collection of data it is common to create a new collection of data with the changes applied, such that there is a reference to the new state and a reference to the old state. When the reference to the old state is lost, then the old state/data is garbage collected. Contrast this to properties, which are updated and modified individually such that the current obj.x and obj.y represent the current (x, y) coordinates.

I'm not sure if these ideas are possible, but they seem quite powerful in theory. Letting data/arrays/keys be enumerated allows processing over the entire data set. Letting properties be observed and subscribed to allows for more interactive object construction, similar to a SetTimer/Scheduler within the object. Finally, methods can be called and used as functions with implicit state or "this".

Sources: https://stackoverflow.com/questions/170 ... observable
Disclaimer: This is just what I think, I don't really program in C#.
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Objects

19 Nov 2018, 12:47

I mean this should be something __Get should be useable for but it simply isn't.
Recommends AHK Studio
iseahound
Posts: 1427
Joined: 13 Aug 2016, 21:04
Contact:

Re: Objects

19 Nov 2018, 13:15

No it should be something different like OnClipboardChange() except it would be OnPropertyChange() instead. Also it would be inside an object, with limited scope (to that object.)
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Objects

19 Nov 2018, 13:35

Ah you just mean implementing Observer patterns for all built in AHK classes?
Rather than implementing a common interface to observe any object.
Recommends AHK Studio
iseahound
Posts: 1427
Joined: 13 Aug 2016, 21:04
Contact:

Re: Objects

19 Nov 2018, 14:41

Well it would be dual to _forin, so it would be a common interface inside an object. (Like a meta function?) The OnPropertyChange() seems intuitive as well, and very user friendly. By separating data and properties the observer interface wouldn't have to check the status of so many property variables. Likewise the enumerator interface is faster because of separation of data and interface. It's a win-win.
_NewEnum is renamed to __forin
I feel like this is a mistake when C# already defines IEnumerable for data and IObservable for streams or asynchronous data.
Maybe Iterator and Observer? I don't think an iterable object should be named _forin, that makes it sounds like it only has one use.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Objects

06 May 2019, 12:36

This is an important topic, I would suggest it be pinned.
I'm glad to see steps towards separating data and interface :thumbup: . (re. the latest alpha release).

I see you decided on the syntax for accessing properties and methods with calculated names. obj.[expr] and obj[expr])() looks better to me, but [] doesn't work to replace %fn%() and %var%. I guess <> could be used, i.e., obj<expr>() and obj<expr>, <fn>() etc...

Cheers.

Edit, <> is ambiguous for concatenation.
Last edited by Helgef on 06 May 2019, 15:22, edited 1 time in total.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Objects

06 May 2019, 13:35

Helgef wrote:
06 May 2019, 12:36
I see you decided on the syntax for accessing properties and methods with calculated names. obj.[expr] and obj[expr])() looks better to me, but [] doesn't work to replace %fn%() and %var%. I guess <> could be used, i.e., obj<expr>() and obj<expr>, <fn>() etc...
I can't say I like any of those honestly.

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 24 guests