Re: What has happened to AutoHotkey?!
Posted: 22 Mar 2023, 16:03
I think one of the most powerful components to AutoHotkey is the extensive set of Windows-oriented libraries to do just about anything within the Windows graphical shell. Other languages have their own sets of libraries, but all targeted at different environments with naught but scraps for Windows desktop automation. AutoHotkey v2 makes it much easier to build these feature-adding libraries that have more complicated functionality, and gives many tools to make using libraries easier (assuming they actually get used by authors).
I certainly have my gripes with many of the changes. However, I think most of the issues with moving to v2 stem from pre-conceived notions from v1, a sprinkle of pretty confusing error messages, and documentation that goes way too hard. I think all three of those are things that will get better over time.
The example from the original post is a prime example of bad documentation. That code focuses to much on what you can do, neglecting to show you what you probably actually want to do. I would rewrite it from to
This conversation extends into the more general question of what happened to subroutines, and why?
Notably, functions are a type of subroutine, so to be clear I'm calling them "functions" and "labelled subroutines".
Label-based subroutines, mind the french, sucked in key ways.
Labelled subroutines were a primary reason that the auto-execute section existed, making it impossible to write code like which has caused untold pain for the past 15 odd years. Introducing the use of matching brace pairs for v2 made it much easier to delineate what constituted a subroutine / function, and what could be considered auto-executable. This has enabled auto-execution to flow seamlessly around subroutine definitions, making code like the above normal.
Labelled subroutines depend on the existence and management of global variables, which pose unique challenges to people writing libraries, but also to people writing just their own every day code. When I'm working with code that GoSubs everywhere, it's frequently impossible to keep track of what state changes will result from the GoSub. It's possible to do something like but at that point you're just making new work when a function manages data flow in a clear-by-default way. Labelled subroutines for built-in events necessitate the creation and use of built-in variables that are inexplicably not quite global, but instead are some weird almost-global per-thread abomination. They're weird, and as far as I can tell are an innovation that exist purely in autohotkey-alike languages.
Labelled subroutines are forced global. Functions give you the option, which you can very easily take.
Labelled subroutines don't nest properly, so if you want to make a new subroutine to handle, say, your GUI events inside of a subroutine or function that created the GUI, you end up tearing your hair out. This has been a huge problem in the past with labels like GuiClose, GuiSize, GuiDropFiles, etc. I acknowledge that you have to be a certain kind of person to run up against this with any frequency, but I am that kind of person.
The biggest point, from a language feature standpoint subroutines entirely duplicate the functionality of functions. The two can fight each other, but given that functions support all label features with some extra left over, functions will always win.
In v2, functions have new tricks that make their replacing labelled subroutines much easier to bear. Notably, they can read global variables now without having to mark them as global (though you must mark them as global to write to them).
You can call them by name as a command, meaning you can call them easier than a v1 labelled subroutine. In v1, you'd write GoSub subroutine, but in v2 you can now write just subroutine for any named function-based subroutine.
If you're build a GUI inside a function, you can now define the event handlers for that GUI also inside the function. And those functions defined inside functions can access and change the variables from the outside super duper easily, referencing just the ones for that instance of the GUI making it super duper easy to make your callbacks affect the variables you want. It's an absolute game changer.
Notably, GUI syntax is still bizarre. However, v1's GUI syntax is also bizarre. If you struggle with it, I understand but I strongly do not believe it's because v2's GUI syntax is inherently worse. This is a prime example of a struggle that exists only when you already know v1, and so it does not make a good metric to judge v2's implementation's friendliness for newcomers.
I don't know where I'm going with all this, except maybe to say that the documentation for v2 is misleading. A lot of the symbol soup can be thrown out, to reveal a more v1-ey language underneath. I do regret the removal of plain-text parameters, and the brief existence of variable de-referencing in strings with no practical replacement. But over all, it makes it better for power-users to create better tools for non-power-users and that's a good thing. It adds many stumbling blocks for people coming from v1, but I don't believe that's avoidable.
In the Discord we've been helping people completely new to v2 for many weeks now, and the biggest stumbling blocks are:
1. I installed v2 but I've copied v1 code from online into my script
2. (Frustratingly) I asked ChatGPT to help me with my code and it spat out absolute hot garbage which I pasted into my script
3. Just regular issues that people would have whether they were using v1 or v2, like not putting braces around indented if else blocks
I certainly have my gripes with many of the changes. However, I think most of the issues with moving to v2 stem from pre-conceived notions from v1, a sprinkle of pretty confusing error messages, and documentation that goes way too hard. I think all three of those are things that will get better over time.
The example from the original post is a prime example of bad documentation. That code focuses to much on what you can do, neglecting to show you what you probably actually want to do. I would rewrite it from
Code: Select all
main := Gui('+Resize')
main.OnEvent('Close', (*) => (wvc := wv := 0))
main.Show(Format('w{} h{}', A_ScreenWidth * 0.6, A_ScreenHeight * 0.6))
Code: Select all
main := Gui("Resize")
main.onEvent "Close", CloseRoutine
main.Show "w" A_ScreenWidth * 0.6 "h" A_ScreenHeight * 0.6
CloseRoutine(thisGui) {
global
; Erase global variables wvc and wv
wvc := unset
wv := unset
}
This conversation extends into the more general question of what happened to subroutines, and why?
Notably, functions are a type of subroutine, so to be clear I'm calling them "functions" and "labelled subroutines".
Label-based subroutines, mind the french, sucked in key ways.
Labelled subroutines were a primary reason that the auto-execute section existed, making it impossible to write code like
Code: Select all
x := "initial"
a::
msgbox %x%
return
y := "initial"
b::
msgbox %y%
return
Labelled subroutines depend on the existence and management of global variables, which pose unique challenges to people writing libraries, but also to people writing just their own every day code. When I'm working with code that GoSubs everywhere, it's frequently impossible to keep track of what state changes will result from the GoSub. It's possible to do something like
Code: Select all
; ... whatever code
; Populate alpha and beta to be processed by Routine
alpha := 1
beta := 2
GoSub Routine ; Saves results in gamma
Labelled subroutines are forced global. Functions give you the option, which you can very easily take.
Labelled subroutines don't nest properly, so if you want to make a new subroutine to handle, say, your GUI events inside of a subroutine or function that created the GUI, you end up tearing your hair out. This has been a huge problem in the past with labels like GuiClose, GuiSize, GuiDropFiles, etc. I acknowledge that you have to be a certain kind of person to run up against this with any frequency, but I am that kind of person.
The biggest point, from a language feature standpoint subroutines entirely duplicate the functionality of functions. The two can fight each other, but given that functions support all label features with some extra left over, functions will always win.
In v2, functions have new tricks that make their replacing labelled subroutines much easier to bear. Notably, they can read global variables now without having to mark them as global (though you must mark them as global to write to them).
You can call them by name as a command, meaning you can call them easier than a v1 labelled subroutine. In v1, you'd write GoSub subroutine, but in v2 you can now write just subroutine for any named function-based subroutine.
If you're build a GUI inside a function, you can now define the event handlers for that GUI also inside the function. And those functions defined inside functions can access and change the variables from the outside super duper easily, referencing just the ones for that instance of the GUI making it super duper easy to make your callbacks affect the variables you want. It's an absolute game changer.
Code: Select all
makeMyGui
makeMyGui() {
guiName := Gui("Resize")
guiName.SetFont "s16"
textControlName := guiName.AddText("c303030", "Width: 9999, Height: 9999")
guiName.onEvent "Size", ResizeMe
guiName.Show "w" 640 "h" 480
ResizeMe(guiName, minMax, width, height) {
; notice here, textName was grabbed automatically from the outside world
textControlName.Text := "Width: " width ", Height: " height
}
}
I don't know where I'm going with all this, except maybe to say that the documentation for v2 is misleading. A lot of the symbol soup can be thrown out, to reveal a more v1-ey language underneath. I do regret the removal of plain-text parameters, and the brief existence of variable de-referencing in strings with no practical replacement. But over all, it makes it better for power-users to create better tools for non-power-users and that's a good thing. It adds many stumbling blocks for people coming from v1, but I don't believe that's avoidable.
In the Discord we've been helping people completely new to v2 for many weeks now, and the biggest stumbling blocks are:
1. I installed v2 but I've copied v1 code from online into my script
2. (Frustratingly) I asked ChatGPT to help me with my code and it spat out absolute hot garbage which I pasted into my script
3. Just regular issues that people would have whether they were using v1 or v2, like not putting braces around indented if else blocks