Functions and Global Variables

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Functions and Global Variables

Post by Lem2001 » 21 Dec 2022, 16:45

I have a sizeable block of code that I need to use in a few different places. Ordinarily I'd just copy and paste the code into the various sections where it's needed (because it's simpler and I know it will just work).

However, I thought I'd try using a function for the first time, and now I have a couple of issues that I could use some help with.

1. My code is triggered by a hotkey, but within that code is another hotkey that can be used depending on the outcome of the first hotkey's action. When I moved this code into a function, AHK complained about the code containing a hotkey, and so I had to dismantle that section to take it out.

2. Now that the code is in 2 separate sections, many of my variables no longer work. So I found the specific ones that needed to be shared across the code blocks and made them global. This mostly worked. But there is one instance where it doesn't.

I have a set of different conditions (each wrapped in its own braced section) that are all similar. I've assigned the same output variable name to each section, so that whichever one gets used, the output var is the same name and can therefore be referenced elsewhere regardless of which code block generated the output.

This no longer works now the code is split, and I can't make the various output vars global because there'd be multiple instances of the same name (which AHK won't accept). And if I have to go through and rename them all to be unique then it's no longer a case of reading one output variable to get my result (which the next hotkey then acts upon).

Does anyone have any tips or general suggestions on how I should be structuring things to get round issues such as this?

I can't post the actual code because it contains a lot of personal info and it would take ages to strip it all out for public posting. So I'm just looking general guidelines here.

User avatar
Chunjee
Posts: 1499
Joined: 18 Apr 2014, 19:05
Contact:

Re: Functions and Global Variables

Post by Chunjee » 21 Dec 2022, 17:00

It sounds like you have a scope issue. ahk has somewhat sensible scope rules.

functions by default don't get to access outside variables.

Examples of ways to access variables in functions.

Code: Select all

var_1 := "AutoHotkey"
var_2 := "----------"
Global var_3 := "ahk" ; Super Global
As the function has Local scope so it will only be able to see the Super Global variable var_3

Code: Select all

fun_1() {
    MsgBox, % var_1 "`n" var_2 "`n" var_3
}
The line Global var_1 allows the use of var_1 but not var_2

Code: Select all

fun_2() {
    Global var_1
    MsgBox, % var_1 "`n" var_2 "`n" var_3
}
The line Global changes the scope of the function to Global allowing the use of both var_1 and var_2

Code: Select all

fun_3() {
    Global
    MsgBox, % var_1 "`n" var_2 "`n" var_3
}
Instead of changing the scope the values can be passed as parameters to the function

Code: Select all

fun_4(var_1, var_2, var_3) {
    MsgBox, % var_1 "`n" var_2 "`n" var_3
}

User avatar
andymbody
Posts: 994
Joined: 02 Jul 2017, 23:47

Re: Functions and Global Variables

Post by andymbody » 21 Dec 2022, 18:42

I have a set of different conditions (each wrapped in its own braced section) that are all similar....

I can't post the actual code because it contains a lot of personal info and it would take ages to strip it all out for public posting. So I'm just looking general guidelines here.
Without code to inspect, I can only make general suggestions.

My first thought, based on you description is to use classes/objects. This will allow variable names that are the same, accommodate similar functionality with very little repeated code lines, modular design, and the ability to reach the individual objects and their members globally as desired.

If you are not familiar with classes/objects (OOP), it's a much different way to handle data and functionality, but has many advantages. If you are experimenting with different styles of coding anyway, I would suggest giving this a go. It will change the way you code and open many new possibilities.

The disadvantage of AHK (v1, not sure about v2) is that the members of the class (static) and instances are not clearly separated. In other words, a static call acts like just another object/instance (except that instances share access to the static members as well). This is much different than most languages where the class does not have access to instance methods. Static members have a clear separation, by default. I felt the need to mention this "gotcha" up front, because it will make your head spin the first time you see it.

Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Functions and Global Variables

Post by Lem2001 » 22 Dec 2022, 15:16

Thank you so much @Chunjee and @andymbody for your detailed replies. They were both incredibly helpful, especially seeing as you had so little concrete information to work with.

I have now managed to get the code fully working! A couple of different approaches worked:

One was making the 2 non-working variables Super Global (I made them global outside of the function).

And the other approach worked by not trying to put those two particular variables into another variable specifically for global use.

What I mean by that is that I thought it might be useful to easily identify global variables, because generally I don't want things to have a wider scope than necessary to avoid unintended outcomes. So I decided to use only names starting with an underscore for any variable that I made global (an easy rule to implement, seeing as I have only just started using functions and global variables for the first time).

Instead of going through all the individual code blocks to add an underscore to multiple instances of the existing variables that needed to be made global, I just did something like this:

Code: Select all

Global _FileText := FileText
Global _InsertedText := InsertedText
Global _CurrentItem := CurrentItem
Global _SelectionTotal := SelectionTotal
Global _TimestampedText := TimestampedText
Global _TimestampedValue := TimestampedValue

There were only 6 that needed to be accessed outside of the function. This meant that I didn't need to mess with my existing code. However, it only worked for the first 4 variables. The last 2 stubbornly refused to be assigned any new variable name. But if I just did this instead, then they all worked. I have no idea why.

Code: Select all

Global _FileText := FileText
Global _InsertedText := InsertedText
Global _CurrentItem := CurrentItem
Global _SelectionTotal := SelectionTotal
Global TimestampedText
Global TimestampedValue
There's nothing particularly different about the last 2 variables, they're generated using pretty similar methods to the others. It's kind of frustrating not knowing why something doesn't work in what appears to be almost identical circumstances, but I'm just grateful that I got it working at all. I have spent so much time on troubleshooting this that I was close to just duplicating the entire code block in the several places where it was needed, which thankfully I don't have to do now, thanks to you guys and your help.

I will definitely look into classes and objects too (testing with a much less complicated code sample) because it sounds very flexible. Of course that's assuming that I can even understand how to use it based on the Help file, which is not exactly the most newbie-friendly resource.

Before that though, I might first try to figure out why the last two variables can't be seen outside the function when put into a different variable name. That's still bugging me, even though I have a workaround that fixes the issue.

User avatar
andymbody
Posts: 994
Joined: 02 Jul 2017, 23:47

Re: Functions and Global Variables

Post by andymbody » 22 Dec 2022, 15:42

The last 2 stubbornly refused to be assigned any new variable name
What do you mean by variable "name"?

You may be aware of this, but... for clarification...

Code: Select all


; This...
Global _TimestampedText := TimestampedText
; means...
_TimestampedText := ; the value found in (TimestampedText) at this particular point an time

; so... if
TimestampedText := "text"
; then...
_TimestampedText := TimestampedText
; means...
_TimestampedText := "text"	; but only at this particular moment

; ...if we later change the value of TimestampedText...
TimestampedText := "new text"
; then...
_TimestampedText := "text"	; is still the same value as originally assigned

; They are completely separate variables and are not linked in any way within your code

; SO...

; the global declaration is probably the equivalent to...  
Global _TimestampedText := TimestampedText := ""
; which would mean...
_TimestampedText := ""  ; throughout all of your code unless you put another value into it manually



Andy

Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Functions and Global Variables

Post by Lem2001 » 22 Dec 2022, 16:43

Oh, wow. Thank you.

I have completely misunderstood what Global _TimestampedText := TimestampedText means and how it works.

I have several braced code blocks that are triggered under different conditions. They all end with one of variables being TimestampedText. So when I do a command like ControlSetText, %EditField%, %TimestampedText% it displays the output of whichever code block was used in that particular instance. If another code block is used next time (e.g. code block b instead of code block a), then block b's output will be 'in' the TimestampedText variable, so that is the output that will be displayed this time when using ControlSetText.

There are multiple TimestampedText throughout the code, but only one _TimestampedText which I made global. I wrongly assumed that by doing _TimestampedText := TimestampedText I was telling AHK to duplicate whatever is in TimestampedText into _TimestampedText. So I thought whenever TimestampedText got updated, then _TimestampedText would also update to reflect that change, and I could then just read _TimestampedText from other functions which can no longer read the original variables ever since I had to split the code to make it into a function.

I assumed that it worked like this because I am getting correct output for the first 4 variables that I listed in my previous post. Clearly I've just accidentally got an outcome that I was seeking despite my variable assignments essentially being trash. I thought I was making progress, but it's now clear that I still don't know what the hell I'm doing! lol.

User avatar
andymbody
Posts: 994
Joined: 02 Jul 2017, 23:47

Re: Functions and Global Variables

Post by andymbody » 22 Dec 2022, 17:45

So I thought whenever TimestampedText got updated, then _TimestampedText would also update to reflect that change
This is what you should be aware of...

By default, variables within AHK (and most languages) are assigned VALUES (characters and numbers). This is called assigned ByValue
  • W := "water"
    b := W ----> "water"
    b becomes the same value as W (at this particular time) (the value is copied from W to b, like copy/paste)
    b and w are different variables, like you and your father are different people. They reference DIFFERENT locations in memory
An alternate, (but not automatic) assignment is By Reference (ByRef). This is what you are describing.
  • W := "water"
    b := &W -----> reference the SAME memory location (the bucket) that holds the value of "water"
    b becomes a pointer to the memory location used by W that holds the value of "water". Therefore the two are "linked" because they REFER to the SAME memory location
    MsgBox % b ----> will display a number that represents the memory location for W - 366478738
    MsgBox % StrGet(b) ----> will display the value stored at that memory location - "water"
BUT...

Unfortunately, (as far as I know) you cannot mix global scope with function scope. So, a global variable cannot be assigned the reference (memory location) of a non-global variable.

Andy

Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Functions and Global Variables

Post by Lem2001 » 22 Dec 2022, 18:09

Thanks for clarifying the distinction between ByValue and ByRef.

So given that I now have 2 separate functions, and the second function needs to be able to read the value of TimestampedText (and a few other variables) from within the first function (from whichever code block happened to 'fill' the variable) what would be your recommended way to share those variables between functions, rather than the haphazard way that I'm doing it at the moment?

By the way, I looked at objects in the AHK help file, and while they seemed really useful, it was way over my head. The description was so conceptual and abstract. I think I've got to learn to walk before I try running.

User avatar
andymbody
Posts: 994
Joined: 02 Jul 2017, 23:47

Re: Functions and Global Variables

Post by andymbody » 22 Dec 2022, 20:24

Lem2001 wrote:
22 Dec 2022, 18:09
what would be your recommended way to share those variables between functions
Three general ways to share variables
1. Use global variables - shared variables that need to be maintained for any length of time, and passed around regularly.
2. Pass variables as arguments during function calls - used to provide input to a function or manipulate the variable data using an operation found in the function. These are usually just brief exchanges of data between two parties.
3. Using file read/writes - use only if you can't do 1 or 2 or to also provide permanent storage of values.

If I understand your case correctly... global variables would be best
By the way, I looked at objects in the AHK help file, and while they seemed really useful, it was way over my head. The description was so conceptual and abstract. I think I've got to learn to walk before I try running.
Yes, classes and OOP can be a challenge to grasp at first. Once you "get it", they make all the sense in the world, but until then, they can make your head spin. :crazy: To be honest, they are actually like real world objects, and we use the concept in the real world everyday, but don't realize it. I had trouble with understanding them at first (coming from years of procedural and top-down coding), but once the AHAH! moment happened, I was hooked... I turn to them regularly now. As a matter of fact, some languages are 100% OOP, which forced me to get busy with it.

If you decide to explore them at some point, try not to pay too much attention to all the jargon (instantiation, inheritance, polymorphism, etc), until you have the basics down. This stuff is important but not enough in the beginning to get bogged down with it. You first need to understand functions and transition into using them regularly. Nearly everything I do is using functions. Functions (called methods in OOP) are an absolute must with OOP. You can't do anything without them.

Sounds like you have quite the project on your hands... I wish you luck with it!
Andy

Post Reply

Return to “Ask for Help (v1)”