Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Class definition syntax for AutoHotkey


  • Please log in to reply
96 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
This post is a reply to IsNull's comments in the AutoHotkey v2 thread, to keep that thread on-topic.

Car.Run(){ ; Allow Functiondefinition directly for a Member

I believe we've already discussed this, and I suggested that syntax. However, after jethrow brought up functions within functions, I started having doubts about whether it should assign the function to the object at load-time or when that line is reached during execution. If at load-time, how would it work if the function definition was nested inside another function? Assigning the function at run-time could be more intuitive, but without the capability to nest functions, you would need to use a global subroutine to initialize the class.

Simply allowing nested functions might be easy, and allowing a function to access it's parent's local variables might also be easy, but not in a way that's useful for objects. This is because each function has exactly one set of local variables. If a function is called before the previous call to that function returns (i.e. due to explicit recursion or thread interruption), its local variables are "backed up" and cleared. When that call returns, the variables are restored. When the only running instance returns, the local variables are cleared. So nested functions can't really provide much flexibility.

I think that many of the concepts used in languages like JavaScript (such as closures and anonymous functions) are probably so far beyond the current (AutoHotkey) interpreter's capabilities that it's best to just do it in a way that works for AutoHotkey. If the feasibility of more advanced features improves at some point, compatibility can be broken with another major version change.

Since realizing the approaches used in other prototype-based languages aren't necessarily feasible for AutoHotkey, I've overcome my initial resistence to the idea of adding class-definition syntax.

class Car
{
   Name := "woot"

   Run(){
      msgbox % this.Name ; this is avaiable in this context
   }
}

; 1. the class keyword is replaced with  := Object()
; 2. Everything in braces in a class definition is in the Context of the given class;
;   Name := "woot" is replaced with Car.Name := "woot"
;   Run(){...} is replaced with Car.Run(){...}
; "this" keyword is avaiable in those methods

So if your code translates to ...
Car := Object()
{
   Car.Name := "woot"

   Car.Run := "Car_Run"
   Car_Run(this) {
      msgbox % this.Name
   }
}
... when is this code executed, to initialize the class? Do you have to define it in the auto-execute section?

How do you create an instance of a Car? To create an object, you call Object(). So to create a Car, why not call Car()? "Class Car" could translate to a function definition (with this := Object() ... return this implied), but then we're back to the tricky question of how well to support nested functions. I think it would be more appropriate to just let the class author define a function, and let that function create and initialize the object. On the other hand, if we're going so far as to introduce class-definition syntax, it seems logical to have some shortcut way of writing a constructor, and therefore some new syntax to indicate we're calling one.

If you define Name := "woot", would you not expect to be able to access Name without writing this.Name in full? I would.

I know that the current behaviour of the semi-automatic "this" parameter confuses some users, and it also imposes certain limitations. For instance, you might think to call this.base.Method(this) to pass control to an overridden version of the method. Unfortunately, this will only work if this.base.HasKey("Method"), not if it is inherited from this.base.base.

As part of a solution, I've been considering implementing "this" as a built-in keyword. Perhaps rather than reserving that variable name everywhere, it could have special meaning only inside class/method definitions.

Being able to call an overridden version of a method can be important, and a "base" keyword could serve that purpose. Rather than referring to "this.base", it would refer to the base object of the enclosing class (i.e. it would refer to the super-class).

My idea in its current form:
; This would have no direct script analog in the sense of a pre-
; processor.  Instead, some work would be done at load-time.

class Foo extends Bar
; Entering class definition.  Create a super-global var "Foo" and store
; an object in it.  If "Bar" exists, store its value in Foo["base"].
{
    ; This is explicitly a declaration, not to be confused with a line
    ; of code to be executed.  Store 0 in Foo["Flavour"].  New objects
    ; deriving from Foo won't receive this value directly, but will
    ; inherit it via the "base" mechanism.
    var Flavour := 0
    
    ; Function declaration.  Since we're in a class definition, instead
    ; of creating a global "Taste" function, create an anonymous
    ; function and store its reference in Foo["Taste"].
    Taste() {
        ; Since Flavour was declared as a class var, we want to be able
        ; to refer to it as "Flavour" in addition to "this.Flavour".
        ; Technically, the preparser only needs to determine that the
        ; class object being defined has a key called "Flavour".  The
        ; same would be true for method calls.
        if Flavour
            return Flavour
        ; This would be processed something like Foo.base.Taste(this),
        ; except that "this" isn't really a parameter.  That is, "this"
        ; will contain the same value, not the value of "base".
        return base.Taste()
    }
    
    ; Constructor, perhaps called via "new Foo(flavour)".
    __New(aFlavour) {
        Flavour := aFlavour
        base.__New()
    }
}


LiquidGravity logged off
  • Guests
  • Last active:
  • Joined: --
Very intriguing idea. The definitions for classes is a must for me. It is the best way I can think of for organizing what may otherwise be spread out through code. I'll look at this a little latter and give more thoughts.

IsNull
  • Moderators
  • 990 posts
  • Last active: May 15 2014 11:56 AM
  • Joined: 10 May 2007

Since realizing the approaches used in other prototype-based languages aren't necessarily feasible for AutoHotkey, I've overcome my initial resistence to the idea of adding class-definition syntax.

Thats what I always thougt, but my lack in knowing any prototype based language did not allow me to say: Stay a way from that. It's just something other, and at least for me its more confusing.

Function-in-Function Design is also somewhat strange to me, I think introducing some new keywords is not bad at all, and makes code better readable.

... when is this code executed, to initialize the class? Do you have to define it in the auto-execute section?

For AHK_L, I held class definitions in Include-Files, so they are interpreted first. The Constructor was always just a simple Function which returned a new Instance of the Object.

But if you want support class definition, I definitivly vote for also introducing the "new" keyword (some others too), and also preloading class definitions. It makes code better readable, as its important when objects get created.

If you define Name := "woot", would you not expect to be able to access Name without writing this.Name in full? I would.

Me either. My initial example was thought as an easy alternative way to support class definitions with the current Object behavior where as I expected you are not comfortable with it. ;) I did not expect that turn of your mind, well it's more than welcome.


As part of a solution, I've been considering implementing "this" as a built-in keyword. Perhaps rather than reserving that variable name everywhere, it could have special meaning only inside class/method definitions.

This would make just sense in compatibility background, and as we anyway break compatibility I vote for reserving the keywords. Just throw exceptions when reserved keywords like "class", "new", "this", "base" are used out of the class context. Even if a Newbie which wont use OOP gets prompted whit "*class* is a reversed Keyword, please use something other" it should be not that hard to understand. Additionally, as more control comes into play, there is some potential for IDE support.

<ot>
For me, a good IDE is something which 1. makes writing code faster, 2. helps to find bugs faster.
If I compare VS2010 with [place here whatever you want], productivity is somewhat extremely higher because of the great IDE.

Having classes might also lead to define variables for a specail class, which then makes an IDE able to support the user with all possible functions.
And this is very Newbie friendly. I'm thinking of somewhat like:

Window win := WinGet("Notepad")
win. <--- as the user writes the dot, all avaiable properties are shown by intellisense.


Thinking of that, it may be possible to provide this help even without declaring variables explicitly. Like the C# preprocess replaces every "var" with the expected type, this might even be possible without declaring anything:

win := GetWindow("NotePad")
win. <--- show all possible properties
This is possible, when Methods can have typed return parameters, so this can be used for build-in functions as well as for UDFs:

; Default function definition -> can return anything
func(param){

}

; Defines the return type. For the beginning this syntax just had to be accepted, nothing more 
;  --> an IDE can use this information to provide Intellisense Help for known Members
type func(param){

}

I see in typing - against most people here - some what of easier usage.

And well, I talked a lot from "IDE". I've some plans to write one, as soon as IA reaches V1. ;)

I'm currently struggling in using WPF or not, as this binds it to windows only. On the other hand with WPF, its extremly easy to create a good user feeling, and I wrote some bigger LOB App in it recently.
The other point is the code analyzing (which will be completely loose coupled from any WPF stuff, so reusing it for non WPF Version will be possible). IA brings a good parser, it may or may not be necessary to use its parser to analyze the syntax. I'm not experienced in analyzing source code, so I'm not that sure if its necessary to (re) use something like IA Token Parser.

If I compare MonoDevelops IDE and VS2010 (which is also using WPF) MonoDev reminds me on VB6 IDE. To make it clear: VS2010 is kind of a showcase how powerful WPF is, specaily when it comes to rendering.
--> <!-- m -->http://blogs.msdn.co... ... art-1.aspx<!-- m --> So, WPF is THE (prooven) tool to write such an App. There are very great OpenSource Resurces which I used sucesfully like the avalondock.

I'm very unhappy that Mono doesn't plan to support WPF, but what ever :) I'll announce this project when there is somewhat to play with.

</ot>

Sorry for that monster response.

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
I fully support a class based syntax and (optional) types for Intellisense support.

I would also very much like to see an IDE with good AutoCompletion.

Also somewhat OT, but the text-editing capabilities of Visual Studio seem rather lame compared to Notepad++. I did not look much into it, but I couldn't find hotkeys for moving a line, duplicating it or doing column selection.

Frankie
  • Members
  • 2930 posts
  • Last active: Feb 05 2015 02:49 PM
  • Joined: 02 Nov 2008
I've never made classes with AutoHotkey. I don't really see the need on small to medium sized projects.

On the subject of IDE support, I think that would be important especially for the medium sized scripts that might be using classes. There should be a better way than adding extra code that serves no purpose other than a comment for the IDE.


but the text-editing capabilities of Visual Studio seem rather lame compared to Notepad++

Try comparing it to emacs... and it's main purpose is to give you specialized support for the language. For example, compiling, debugging, easily going to the declaration/definition of a function.
aboutscriptappsscripts
Request Video Tutorials Here or View Current Tutorials on YouTube
Any code ⇈ above ⇈ requires AutoHotkey_L to run

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
I think that both aspects are necessary. For debugging I often use Scite4Autohotkey, but for writing the scripts I keep turning back to Notepad++ because I like its text editing abilities better. It would be nicer if the IDE had similar text editing support (which Scite4Autohotkey probably has, but I have not investigated its configuration files).

LiquidGravity
  • Members
  • 156 posts
  • Last active: Oct 11 2014 04:11 PM
  • Joined: 26 Jan 2009

class Car
{
   Name := "woot"

   Run(){
      msgbox % this.Name ; this is avaiable in this context
   }
}

; 1. the class keyword is replaced with  := Object()
; 2. Everything in braces in a class definition is in the Context of the given class;
;   Name := "woot" is replaced with Car.Name := "woot"
;   Run(){...} is replaced with Car.Run(){...}
; "this" keyword is avaiable in those methods

So if your code translates to ...
Car := Object()
{
   Car.Name := "woot"

   Car.Run := "Car_Run"
   Car_Run(this) {
      msgbox % this.Name
   }
}


Just to clarify - you are thinking of allowing the code in the first in the script and translating it at run time into the code in the second - correct?

I don't really like the 'this' much either, although if it was to be used it would make sense to me to use it in the various other places where it would work. For example in Loops A_Index changed to this.Index

How strict would it be about character case? I would hope it is indifferent. I hate having something so simple as a capital letter cause a debugging session.

Would the following work (also removed the 'this' for sake of argument):
class Car
{
   Name := "woot"
   Local Color := "Red" ; Color can only be accessed by the class
   SetColor(val){
      Color := val ; Can set the color using a function of the class
   }
   GetColor(){
      Return Color ; Can get the color using a function of the class
   }
   Run(){
      msgbox % Name
   }
}

; So in use it would act as such:
MyCar as new Car() ; Make a new Car
msgbox % MyCar.Name ; Should display "woot" without parenthesis
MyCar.Run ; Should display "woot" without parenthesis
msgbox % MyCar.Color ; Displays a blank msgbox or rather displays an error if so setup.
msgbox % MyCar.GetColor() ; Should display "Red" without parenthesis
MyCar.SetColor(Green) ; Should set the local Color variable to Green
msgbox % MyCar.GetColor() ; Should now display "Green" without parenthesis
I would think that a Class definition could be anywhere like a function:

The #Include directive may be used (even at the top of a script) to load functions from an external file.

Explanation: When the script's flow of execution encounters a function definition, it jumps over it (using an instantaneous method) and resumes execution at the line after its closing brace. Consequently, execution can never fall into a function from above, nor does the presence of one or more functions at the very top of a script affect the auto-execute section.



infogulch
  • Moderators
  • 717 posts
  • Last active: Jul 31 2014 08:27 PM
  • Joined: 27 Mar 2008

I would think that a Class definition could be anywhere like a function

Whatever syntax we decide, or however it's supposed to be "interpreted" or whatever it's "converted into," if I was limited to one point on this whole discussion, this would be it.

The fact that autohotkey functions do not need to be parsed (i.e. the program flow run over them) before they are called is a very important feature and I would expect to extend the same logic to their parents, classes.

trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008

Being able to call an overridden version of a method can be important...

Could the ability to adjust the visibility of variables and functions within classes be provided (i.e. public, protected, private)?
-trueski-

IsNull
  • Moderators
  • 990 posts
  • Last active: May 15 2014 11:56 AM
  • Joined: 10 May 2007

There should be a better way than adding extra code that serves no purpose other than a comment for the IDE.

Typing has much more things than just providing IDE support. Actually, typing is also something which a compiler can use to produce more efficient code.
It is questionable how strong a interpreter and IDE is tied to each other.
Don't forget, first we should define a Syntax. Step two should be "how to implement that".

The fact that autohotkey functions do not need to be parsed (i.e. the program flow run over them) before they are called is a very important feature and I would expect to extend the same logic to their parents, classes.

++
When you create classes with the new Keyword, they shouldn't be "interpreted" first to exist.

Just to clarify - you are thinking of allowing the code in the first in the script and translating it at run time into the code in the second - correct?

Nomore ;) My first idea was to have class syntax even if Lexikos won't change anything in the interpreter and stay with the prototyped objects. Build in support with some additional keywords is much more appreciated.

I don't really like the 'this' much either, although if it was to be used it would make sense to me to use it in the various other places where it would work. For example in Loops A_Index changed to this.Index

I dont get your point. First you don't like the "this" (what do you mean with not liking "this"? How to replace it?) on the other hand you want it to have everywhere, even in non OOP scenarios? Just keep the A_Index var like it is.
You don't have to use "this" always, but there are situations where it's necessary to say which variable you mean, also, it makes code better readable.

How strict would it be about character case? I would hope it is indifferent. I hate having something so simple as a capital letter cause a debugging session.

While I like casing in C# I'm strongly against it for any script language! No need to make live more complicated.

Could the ability to adjust the visibility of variables and functions within classes be provided (i.e. public, protected, private)?

OOP lives form data encapsulation so control access modifiers is definitely important. (Beside that, an IDE can hide inaccessible Methods to make life easier)

Additionally, classes should be completely capsuled from the script. No globals should be visible inside classes.

guest3456
  • Members
  • 1704 posts
  • Last active: Nov 19 2015 11:58 AM
  • Joined: 10 Mar 2011
i'm not qualified to enter this discussion, but i will just say..

My idea in its current form:

; This would have no direct script analog in the sense of a pre-
; processor.  Instead, some work would be done at load-time.

class Foo extends Bar
; Entering class definition.  Create a super-global var "Foo" and store
; an object in it.  If "Bar" exists, store its value in Foo["base"].
{
    ; This is explicitly a declaration, not to be confused with a line
    ; of code to be executed.  Store 0 in Foo["Flavour"].  New objects
    ; deriving from Foo won't receive this value directly, but will
    ; inherit it via the "base" mechanism.
    var Flavour := 0
    
    ; Function declaration.  Since we're in a class definition, instead
    ; of creating a global "Taste" function, create an anonymous
    ; function and store its reference in Foo["Taste"].
    Taste() {
        ; Since Flavour was declared as a class var, we want to be able
        ; to refer to it as "Flavour" in addition to "this.Flavour".
        ; Technically, the preparser only needs to determine that the
        ; class object being defined has a key called "Flavour".  The
        ; same would be true for method calls.
        if Flavour
            return Flavour
        ; This would be processed something like Foo.base.Taste(this),
        ; except that "this" isn't really a parameter.  That is, "this"
        ; will contain the same value, not the value of "base".
        return base.Taste()
    }
    
    ; Constructor, perhaps called via "new Foo(flavour)".
    __New(aFlavour) {
        Flavour := aFlavour
        base.__New()
    }
}


this is A LOT more intuitive/familiar for me and i like it a lot

trueski
  • Members
  • 121 posts
  • Last active: Jun 25 2014 09:12 PM
  • Joined: 08 Apr 2008

i'm not qualified to enter this discussion, but i will just say this is A LOT more intuitive/familiar for me and i like it a lot


sorry, one of my favorite quotes...

And I do agree with you on it being more intuitive!
-trueski-

LiquidGravity logged off
  • Guests
  • Last active:
  • Joined: --
My issue with the 'this' is the confusion it can cause for new users when jumping around to different areas gives different results. I understand it is sometimes a necessary evil though. Also my idea was not to replace A_Index and similar outright but to allow the alternative this.Index for consistency in a script. Its just an idea I bring for discussion not necessarily a good one.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

This would make just sense in compatibility background, and as we anyway break compatibility I vote for reserving the keywords. Just throw exceptions when reserved keywords like "class", "new", "this", "base" are used out of the class context.

"Class" does not need to be reserved any more than "local" or "MsgBox". "New" is only ambiguous when followed by a variable name (i.e. of a variable containing a class object) without any operator in between, but maybe it should be reserved to avoid confusion. "Base" needs to know which class the current function is part of, but that might be impossible when the function isn't enclosed in a class definition. Elsewhere it can just be an ordinary variable. On the other hand, "this" can be useful in any function (but not in global scope). It could be treated as a variable if its first usage is an assignment, or if it's declared as a variable; but I'm not sure it would be worth it.

There should be a better way than adding extra code that serves no purpose other than a comment for the IDE.

+1. "foo := new bar()" should be easy to parse, although there are more difficult permutations. For other cases, you can just use regular comments (which could be interpreted by the IDE).

How strict would it be about character case?

I see no reason it should be different to the rest of AutoHotkey. Class names are really just variable names. Class variables (if they are supported), aren't really variables (in the sense of ByRef, VarSetCapacity, etc.) but fields in the object. Objects are case-insensitive.

Would the following work

No. Objects don't have the capability for access control. Color could be translated to this[automatic_private_key], but it would be tricky (to implement and document/explain) and IMO not worth it. It would also still be accessible via an enumerator/for-loop. On the other hand, you can store private data like this:
Func(obj, name, prm*) {
    static private_data := {}
    if prm.MaxIndex() ; Value was given.
        return private_data[obj, name] := prm[1]
    else
        return private_data[obj, name]
}
Of course, Func() can be called from anywhere, but that's just an example. It need not be exposed outside of the function.

For Name := "woot", I think that an explicit declaration should be required (i.e. var Name := "woot") since other code wouldn't be valid in that context.

MyCar as new Car() ; Make a new Car

Where did you get "as" from? Use an assignment.

I would think that a Class definition could be anywhere like a function:

That was my point of asking "when is this code executed" about IsNull's example. Class definitions, like function definitions, wouldn't have that problem.

Actually, typing is also something which a compiler can use to produce more efficient code.

While that makes sense for IA, it's irrelevant for AutoHotkey.

When you create classes with the new Keyword, they shouldn't be "interpreted" first to exist.

This is one reason I'm going to change variable name resolution to be done (for expressions) during the expression parsing stage. At this stage, all function and class definitions have been processed. Well, almost...
Func() {
  foo := new Bar()  ; Resolve "Bar" - no such variable, so assume local.
  Bar_Include()  ; Auto-include Bar.ahk, now "Bar" exists as a super-global...
}
OTOH, maybe it's preferable for "new Bar" to invoke the func lib auto-include mechanism, so you don't need to call any conventional functions. :idea:

OOP lives form data encapsulation so control access modifiers is definitely important.

You can just use a unique prefix for private members and discourage users from accessing them directly. If they go ahead and do it anyway, that's their problem.

Additionally, classes should be completely capsuled from the script. No globals should be visible inside classes.

I don't see why they shouldn't be capable of using global variables. However, it would probably be a big benefit to allow static class variables. (At the very least, one could access ClassName.Var directly, since ClassName is just a variable containing an object.)

My issue with the 'this' is the confusion it can cause for new users when jumping around to different areas gives different results.

I don't get it.

Also my idea was not to replace A_Index and similar outright but to allow the alternative this.Index for consistency in a script.

What's it consistent with? This.Foo accesses the Foo property of the object This. Why should loops automatically take over the This keyword? What if you want to use a loop inside a class method?

Off-topic:

It would be nicer if the IDE had similar text editing support

SciTE and Notepad++ are both based on the same text editing component, Scintilla...

I did not look much into it, but I couldn't find hotkeys for moving a line, duplicating it or doing column selection.

Ctrl+C and Ctrl+X copy or cut the current line if there's no selection, then Ctrl+V will insert it above the current line. Column selection is done by holding Alt, the same as Scintilla-based editors (like SciTE and N++). One minor thing VS does better: if you paste after using column select, it pastes on each line, not just the one with the caret. Both VS and Scintilla support using column select as a sort of multi-line insertion point. However, VS doesn't seem to support multiple isolated insertion points (e.g. by holding Ctrl in SciTE/N++).

Frankie
  • Members
  • 2930 posts
  • Last active: Feb 05 2015 02:49 PM
  • Joined: 02 Nov 2008
For large classes, would support across multiple files be possible? For example in C++ (even in AutoHotkey source) members of a class can be defined across multiple files. Though the current set-up, based on my limited knowledge, reflects more of a Java or C# class definition. If those can span multiple files, maybe we can get ideas from them.

Also, what determines the name for the constructor? Can we have deconstructors? The latter would be helpful for class wrappers of DLLs that need to release or do similar actions before exiting the script. Deconstructors should also be called when an class object is assigned to another value, even if that value is of the same class type.
foo := new bar() ; Call constructor
foo := "some other value" ; Call deconstructor

btw: I am starting to see the practicality of classes in AutoHotkey.
aboutscriptappsscripts
Request Video Tutorials Here or View Current Tutorials on YouTube
Any code ⇈ above ⇈ requires AutoHotkey_L to run