fat-arrows and methods Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
iseahound
Posts: 1459
Joined: 13 Aug 2016, 21:04
Contact:

fat-arrows and methods

Post by iseahound » 03 Oct 2019, 22:42

I started with some of Lexikos' code:

Code: Select all

counter := new SecondCounter
counter.Start
Sleep 5000
counter.Stop
Sleep 2000

; An example class for counting the seconds...
class SecondCounter {
    __New() {
        this.interval := 1000
        this.count := 0
        ; Tick() has an implicit parameter "this" which is a reference to
        ; the object, so we need to create a function which encapsulates
        ; "this" and the method to call:
        this.timer := ObjBindMethod(this, "Tick")
    }
    Start() {
        SetTimer this.timer, this.interval
        ToolTip "Counter started"
    }
    Stop() {
        ; To turn off the timer, we must pass the same object as before:
        SetTimer this.timer, "Off"
        ToolTip "Counter stopped at " this.count
    }
    ; In this example, the timer calls this method:
    Tick() {
        ToolTip ++this.count
    }
}
and compressed it a bit using some v2 techniques...

Code: Select all

(counter := new SecondCounter), counter.Start(), Sleep(5000), counter.Stop(), Sleep(2000)

; An example class for counting the seconds...
class SecondCounter {

    interval := 1000
    timer := () => ToolTip(++this.count)

    Start(n := 0) {
        this.count := n
        SetTimer this.timer, this.interval
        ToolTip "Counter started"
    }
    Stop() {
        SetTimer this.timer, "Off"
        ToolTip "Counter stopped at " this.count
    }
}
and compressed it some more...

Code: Select all

(counter := new SecondCounter), counter.Start(), Sleep(5000), counter.Stop(), Sleep(2000)

; An example class for counting the seconds...
class SecondCounter {

    interval := 1000
    timer := () => ToolTip(++this.count)
    Start(n := 0) => ((this.count := n), SetTimer(this.timer, this.interval), ToolTip("Counter started"))
    Stop() => (SetTimer(this.timer, "Off"), ToolTip("Counter stopped at " this.count))
}
but trying to make it into a prototype object is near impossible.

Code: Select all

; An example class for counting the seconds...
SecondCounter := {interval : 1000
, timer : () => ToolTip(++this.count)}
.DefineMethod("Start", (n := 0) => ((this.count := n), SetTimer(this.timer, this.interval), ToolTip("Counter started")))
;.DefineMethod("Stop", () => (SetTimer(this.timer, "Off"), ToolTip("Counter stopped at " this.count)))
(counter := SecondCounter), counter.Start(), Sleep(5000), counter.Stop(), Sleep(2000)
Just trying to see if one-liners are possible, and why are prototype objects like destroyed in v2. Function definitions inside objects are difficult and round-about.

swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: fat-arrows and methods

Post by swagfag » 04 Oct 2019, 03:24

the this in , timer : () => ToolTip(++this.count)} doesnt refer to anything. u cant define a property of this kind that way, u have to use .DefineProp():

Code: Select all

.DefineProp('timer', {get: this => () => ToolTip(++this.count)})
(counter := SecondCounter) this only reassigns the prototype(if can even call it that. for all ahk cares SecondCounter is just another regular object at this point)
use (counter := {Base: SecondCounter}) to "new up" instances of ur "class"/prototype/whatever

ur .DefineMethods' signatures u should also change to include an explicit this as their first parameter

User avatar
kczx3
Posts: 1648
Joined: 06 Oct 2015, 21:39

Re: fat-arrows and methods

Post by kczx3 » 04 Oct 2019, 08:16

I can't imagine why anyone would choose that last syntax over the class syntax though.

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: fat-arrows and methods

Post by Helgef » 04 Oct 2019, 08:33

Perhaps literal object definitions could allow definitions of method and dynamic properties, so an alternative to

Code: Select all

class SecondCounter {
    interval := 1000
    timer := () => ToolTip(++this.count)
    Start(n := 0) => ((this.count := n), SetTimer(this.timer, this.interval), ToolTip("Counter started"))
    Stop() => (SetTimer(this.timer, "Off"), ToolTip("Counter stopped at " this.count))
}
counter := SecondCounter.new()
could be something like,

Code: Select all

counter := {
    interval, 1000,
    timer, (this) => ToolTip(++this.count),
    Start(n := 0) => ((this.count := n), SetTimer(this.timer, this.interval), ToolTip("Counter started")),
    Stop() => (SetTimer(this.timer, "Off"), ToolTip("Counter stopped at " this.count))
}
(note, alternative not equivalent). Also, then {} couldn't be a shorthand for calling object().

Cheers.

Edit, it would still be {prop : value} in the above, not {prop, value} as I wrote.
Last edited by Helgef on 05 Oct 2019, 03:40, edited 1 time in total.

iseahound
Posts: 1459
Joined: 13 Aug 2016, 21:04
Contact:

Re: fat-arrows and methods

Post by iseahound » 04 Oct 2019, 09:59

kczx3 wrote:
04 Oct 2019, 08:16
I can't imagine why anyone would choose that last syntax over the class syntax though.
Might want to dynamically generate an object. Something like pickle in Python or object.toString() and reviving the object is really useful for communication across scripts or users. Sure the object can be generated in a class format, but an AHK script with a string that contains a class definition of an object can't do anything with it. (cause a class has to be present at load-time)

iseahound
Posts: 1459
Joined: 13 Aug 2016, 21:04
Contact:

Re: fat-arrows and methods

Post by iseahound » 04 Oct 2019, 10:28

Code: Select all

; An example class for counting the seconds...
SecondCounter := {}
SecondCounter.DefineProp("count", {get : (this) => 0, set : (this) => this})
SecondCounter.DefineProp("interval", {get : (this) => 1000})
SecondCounter.DefineProp("timer", {get : (this) => ToolTip(++this.count)})
MsgBox SecondCounter.interval
SecondCounter.DefineMethod("Start", (this) => (SetTimer(this.timer, this.interval), ToolTip("Counter started")))
;.DefineMethod("Stop", (this) => (SetTimer(this.timer, "Off"), ToolTip("Counter stopped at " this.count)))

(counter := SecondCounter), counter.Start(), Sleep(5000), counter.Stop(), Sleep(2000)
Oh wow prototyping an class object is absolute cancer. I have no idea what's happening, I've got the timer to work and the interval set. I thought the fat arrow syntax would be optional, but the more I try to create a prototype object the more frustrated I get as I have to fight against this language

EDIT: I don't even begin to figire out how to instansiate a prototype object with new(), tried setting it as a .prototype but New() isn't defined or something :(

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: fat-arrows and methods  Topic is solved

Post by Helgef » 04 Oct 2019, 11:50

I've got the timer to work.
It doesn't seem like it.
SecondCounter.DefineProp("count", {get : (this) => 0, set : (this) => this})
Makes no sense. You are also not binding anything to the timer function, what do you expect this will be?
EDIT: I don't even begin to figire out how to instansiate a prototype object with new(), tried setting it as a .prototype but New() isn't defined or something
See :arrow: class.new().

An example,

Code: Select all

; An example class for counting the seconds...
SecondCounter := Class.New()
SecondCounter.base := Object
SecondCounter.prototype := p := { __class : 'Counter'}
SecondCounter.defineMethod 'new', (this) => {base : this.prototype, count : 0, interval : 1000}
p.defineMethod 'timer', (this) => tooltip(++this.count)
p.DefineMethod("Start", (this) => (SetTimer(this.timerRef := this.getmethod('timer').bind(this), this.interval), ToolTip("Counter started")))
p.DefineMethod("Stop", (this) => (SetTimer(this.timerRef, "Off"), ToolTip("Counter stopped at " this.count)))

counter := new SecondCounter
counter.Start()
Sleep(5000)
counter.Stop()
Sleep(2000)
Cheers.

iseahound
Posts: 1459
Joined: 13 Aug 2016, 21:04
Contact:

Re: fat-arrows and methods

Post by iseahound » 04 Oct 2019, 16:14

Fantastic. I didn't realize class.New() was the actual class object, always thought class was a substitute like how we use "string" for strings.

Code: Select all

; An example class for counting the seconds...
SecondCounter := Class.New()
SecondCounter.base := Object
SecondCounter.prototype := p := { __class : 'Counter'}
This meta data for outlining how to construct a class is very helpful. Thanks, Helgef.

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

Re: fat-arrows and methods

Post by lexikos » 04 Oct 2019, 21:29

Helgef wrote:
04 Oct 2019, 08:33
Perhaps literal object definitions could allow definitions of method and dynamic properties, so [...]

Code: Select all

counter := {
    interval, 1000,
    timer, (this) => ToolTip(++this.count),
    Start(n := 0) => ((this.count := n), SetTimer(this.timer, this.interval), ToolTip("Counter started")),
    Stop() => (SetTimer(this.timer, "Off"), ToolTip("Counter stopped at " this.count))
}
Thanks for posting this. ECMAScript 6 supports method definitions in object literals, such as:

Code: Select all

counter = {
    Start(n) {
       //...
    }
};
I had already assumed that I would implement this eventually, but parsing a block within an expression is not feasible with the current design. Recognizing a fat arrow function (which can obviously already be parsed within an expression) within an object literal might be somewhat easier, so perhaps it could be implemented sooner.
Also, then {} couldn't be a shorthand for calling object().
You can almost count on {...} not remaining equivalent to object(...). As far as I am concerned, we're better off without a way of overriding what {} does, now that there is a better mechanism for extending the functionality of all objects. (It was useful for academic purposes, such as prototyping future versions of the language, as I did with Object.ahk.)

Currently there is a limit on the number of tokens in an expression. I already have code to remove this limit, but it's of limited use because function calls are still limited to 255 parameters, and this includes Array() and Object(). You could create an array of 255 arrays of 255 arrays (if it fits within the max line length), but not an array of 256 items. (The line length limit will also be removed eventually.) These two functions and Map() are the only ones likely to require so many parameters, so one of the solutions I had considered was to transparently divide long literals into multiple calls. This also has the benefit of not requiring as much "stack" space (although the stack I'm talking about might be allocated from heap memory anyway).

Object literals currently allow expressions for the key, in a way that is inconsistent with usage of properties elsewhere. If the key is always a constant, it becomes easier to optimize and validate (preventing duplicates at load-time). The list of key literals can be built at load time and passed as a single unit (perhaps in a way that bypasses the stack), so there are half as many parameters being individually evaluated and pushed onto the stack, and construction of the new object can probably be optimized as well. This would mean overriding Object() would not work, at least how it is now.

I expect to remove Object(), Array() and the new operator prior to the v2.0 release.

iseahound
Posts: 1459
Joined: 13 Aug 2016, 21:04
Contact:

Re: fat-arrows and methods

Post by iseahound » 04 Oct 2019, 22:11

I like where those ideas are going. Practically, AutoHotkey has and always be a prototype based language, so the focus on improving the prototype experience is very central to the language. However, at the moment, the ability to call .New() on a user defined object requires a few lines of boilerplate as Helgef has shown. Hopefully this can be minimized. Removing the new operator is probably for the best. And erase some confusion new users may have between .New() and .Clone() at the same time. Removal of limits is always appreciated.

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

Re: fat-arrows and methods

Post by lexikos » 04 Oct 2019, 23:55

(I suppose this is getting off-topic.)

I consider AutoHotkey neither a prototype-based language nor an object-oriented one. When objects were added, long after the language began, objects were prototype-based. Then there came COM objects, which are not prototype-based (and built-in objects, which were not prototype-based until recently). If changes to an object and its bases are locked down, there is virtually no difference between prototype-based and class-based (but still weakly-typed). In this case we're talking about creating objects ad hoc/dynamically; whether they are based on a generic object class or prototype is irrelevant. Mutability isn't a requirement for something to qualify as prototype-based, nor is immutability required for a class-based design.

What does it mean to call New() on an instance? New() on a class creates an object derived from the class' Prototype, sets its base to the class object and executes variable initializers (__Init) and the constructor (__New). For an object based on another instance, the first instance has already been constructed (presumably from scratch), and the values of its properties should automatically be inherited by the new object, not be re-initialized. It would be a different operation, which should perhaps have a different name. Since it isn't typically valid in the first place, it isn't defined by default.

As you should know by now, repetition of boilerplate code can and should be reduced by defining a function or class/method. In this case the boilerplate code is extremely trivial. If you are constructing a class, you should specify the super-class anyway, and it might not be Object. There is no requirement that the class have a Prototype (if it's an abstract class) or that the Prototype be dedicated to that class, or based on the super-class' Prototype.

What Helgef has shown is not how to "call .New() on a user defined object", but how to construct a class programmatically. The purpose of New() is not to replicate the v1 new operator, but to instantiate a class (which is also the purpose of the v1 operator, although you could misuse it for other things). A class object supports static methods and properties, and usually supports instantiation. You don't necessarily need to follow this pattern.

Maybe you don't really want to "call .New() on a user defined object", but "create an object based on another object". Most of what New() does is to support the class system. For prototype-based objects, you can just create an object, set its base and perform your own initialization.

Here's a few more ways to call "call .New() on a user defined object":

Code: Select all

class userDefinedObject {
}
x := userDefinedObject.new()

Code: Select all

class PrototypeBased {
    New() => {base: this}
}
userDefinedObject := PrototypeBased.new()
x := userDefinedObject.new()

Code: Select all

userDefinedObject := {}
userDefinedObject.DefineMethod "New", this => {base: this}
x := userDefinedObject.new()

Post Reply

Return to “Ask for Help (v2)”