Page 1 of 1

VarIsInit/VarExist or static for global variables

Posted: 04 Feb 2019, 15:26
by jeeswg
Introduction

- IsInit(var)/VarIsInit(var): check if a variable has a value (without triggering #Warn).
- Perhaps: report 2(/A/B) for built-in variables.

- VarExist(VarNameAsStr): check if a variable exists.
- In simple terms: check if a variable is in ListVars.
- More specifically: check if a variable appears explicitly in the script (or if a variable with that name has been created dynamically at runtime).

- Note: a variable can exist, but have no value.
- E.g. Sleep, % var, var may never be given a value, but due to its presence in the script, it 'exists', and so appears in ListVars.

Rationale for IsInit/VarIsInit

- With a script like this, and with #Warn mode on, you get a classic problem:
Warning: This variable has not been assigned a value.

Code: Select all

#Warn

q:: ;increment value
if (var = "")
	var := 0
var++
MsgBox, % var
return

/*
;ideal solution:
q:: ;increment value
if !IsInit(var)
	var := 0
var++
MsgBox, % var
return

;ideal solution (refactored):
q:: ;increment value
var := IsInit(var) ? var+1 : 1
MsgBox, % var
return

;a workaround: use a function and a static variable:
q:: ;increment value
MsgBoxAndIncValue()
return

MsgBoxAndIncValue()
{
	static var := 0
	var++
	MsgBox, % var
}
*/

- Here's a more complex example:

Code: Select all

#Warn

q:: ;replace text in selected text
;static vGblBefore := ""
;static vGblAfter := ""

Clipboard := ""
SendInput, ^c
ClipWait, 3
if ErrorLevel
{
	MsgBox, % "error: failed to retrieve clipboard text"
	return
}
vText := Clipboard

InputBox, vGblBefore,, before:,,,,,,,, % vGblBefore
InputBox, vGblAfter,, after:,,,,,,,, % vGblAfter

vText := StrReplace(vText, vGblBefore, vGblAfter)
Clipboard := vText
SendInput, ^v
return
- The current best solution is to anticipate all of those global variables and define them as blank or 0 in the auto-execute section.
- The best solution might be an IsInit/VarIsInit/IsNull function, which should *not* trigger #Warn.
- Another possible solution is to wrap everything as functions (and use 'static' in the function's local namespace).
- Another solution could be to allow 'static' (or equivalent) in the global namespace. That way you can keep the variable initialisation and the code proper together. (Versus have the variable initialisation moved to the auto-execute section.)
- ... (Perhaps static var := "" for the same variable could be allowed multiple times, but only the last (or first) one would be stored, before the script starts.)
- Or perhaps there is another solution or keyword that might be appropriate, e.g. 'execute a line once only', the first time you see it, but never again.

- [EDIT:] Btw 'try' does not work as a solution for this, this raises an error:

Code: Select all

#Warn
try Var := VarWithNoValue

Rationale for VarExist

- E.g. an object may contain 1000 constants, 10 of those appear in the script, as variables with no values, and so you could check for each constant whether a variable existed, and assign values to those 10 variables, rather than create 1000 variables.
- E.g. for use for with WM (window message) constants or xl (MS Excel) constants.
- It also means that if you check ListVars, it usefully lists only the constants that are *used* by the script, rather than cluttered with every possible constant.

- Thanks for reading. Please notify of any similar threads.

Re: VarExist or static for global variables

Posted: 09 Jun 2019, 09:10
by jeeswg
- In some situations, VarSetCapacity can be used as a pseudo-VarIsInit function.
- E.g. for the example above, adding in these lines before the InputBox works to avoid a #Warn error:

Code: Select all

vGblBefore := VarSetCapacity(vGblBefore) ? vGblBefore : ""
vGblAfter := VarSetCapacity(vGblAfter) ? vGblAfter : ""

- Here is an additional example:

Code: Select all

#Warn

q:: ;toggle - test VarSetCapacity as a pseudo-VarIsInit
if !VarSetCapacity(var)
	var := 1
else
	var := !var
MsgBox, % var
return

w:: ;toggle - test VarSetCapacity as a pseudo-VarIsInit one-liner
var := VarSetCapacity(var) ? !var : 1
MsgBox, % var
return

e:: ;toggle - test without VarSetCapacity (error message the first time, if #Warn on)
if !var2
	var2 := 1
else
	var2 := !var2
MsgBox, % var2
return

- [EDIT:] I noticed some curious behaviour re. VarSetCapacity, I am surprised that var1 and var2 do not report the same capacity. And so I would completely avoid using VarSetCapacity as a pseudo-VarIsInit function in AHK v1:

string/numeric caching in AHK v1 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=69454

Code: Select all

;queries: why does VarSetCapacity on var1 give 6

;var1 := 0
;var2 := 1
;var3 := 0+0
;var4 := 1+0
;MsgBox, % VarSetCapacity(notavar) ;0
;MsgBox, % VarSetCapacity(var1) ;6
;MsgBox, % VarSetCapacity(var2) ;0
;MsgBox, % VarSetCapacity(var3) ;0
;MsgBox, % VarSetCapacity(var4) ;0

Re: VarExist or static for global variables

Posted: 09 Jun 2019, 10:00
by nnnik

Code: Select all

a := {}
c := ""
VarSetCapacity(c, 0)
d := 0 + 0
e := 0
Msgbox % VarSetCapacity(a)
Msgbox % VarSetCapacity(b)
Msgbox % VarSetCapacity(c)
Msgbox % VarSetCapacity(d)
Msgbox % VarSetCapacity(e)
VarSetCapacity measures the size of the internal string buffer is the exact description.
It has no use as a VarExists function.

Re: VarExist or static for global variables

Posted: 09 Jun 2019, 10:35
by Helgef
[edit:]a quote to clarify,
functions wrote:Any non-dynamic reference to a variable creates that variable the moment the script launches
[end edit]
Concepts/Variables wrote:In AutoHotkey, variables are created simply by using them
You probably want to ask for a isVarInitialised function. An actual varExist function would take a string as input. If you want a varExist function which takes a variable as input, here you go,

Code: Select all

jee_varexist(byref var){
	if isbyref(var)
		return true
	throw ; invalid use
}
I don't think we should add any means to facilitate the use of bad practice.
Another solution could be to allow 'static' in the global namespace. That way you can keep the variable creation and the code proper together.
Again, you mean var initialisation, not creation. lexikos already suggested auto expr and / or auto { ... } :arrow: here , that seems more generally useful.

Also, in addition to nnnik's examples, consider that a local variable might be uninitialised but still have a capacity greater than zero from an earlier instance of the function. For example,

Code: Select all

#warn
f()
f()
f(){
	if !VarSetCapacity(var)
		var := 1
	else
		var := !var
	MsgBox, % var
	return
}
Cheers.

Re: VarExist or static for global variables

Posted: 09 Jun 2019, 11:48
by jeeswg
@nnnik:

'VarSetCapacity measures the size of the internal string buffer is the exact description.'
This does not appear to be the whole story. In my example above, there is an anomaly, and so that statement is potentially misleading.

'[VarSetCapacity] has no use as a [VarIsInit] function.'
This is potentially misleading, although it can't be used as a *general* VarIsInit function, in many key scenarios it can be used to perform a VarIsInit role (and bypass #Warn errors). E.g. if you know a variable will definitely contain a string or a known value (with a known capacity).

I would not recommend VarSetCapacity as a general VarIsInit (or VarExist) function, however, I will not make any final conclusion over what it *can* be used for, until the anomaly is cleared up.

@Helgef:

TERMS

You cited 'variables are created simply by using them', but I think this is unclear (potentially misleading) because 'using' would imply 'executing' to many/most people, whereas it's by simply being present in a script that a variable is 'created' (i.e. that it appears in ListVars).

To clarify terms:

Some of the issues of variable creation/existence/initialisation terminology are discussed here:
Suggestions on documentation improvements - Page 15 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=86&t=1434&p=190845#p190845

In simple terms, you might say: creation/existence = 'does it appear in ListVars', and initialisation = 'does it have a value'.
(Whereas normal users, unfamiliar with these specific definitions of standard English words, might say something like: a variable doesn't exist if it doesn't have a value.)

This:
'In AutoHotkey, variables are created simply by using them.'
Sounds like:
'In AutoHotkey, variables are created by executing lines of code.' [not true, unless creating a variable dynamically]
I might say something like:
'In AutoHotkey, variables are created simply by their presence in the script.'
'In AutoHotkey, variables exist simply by their presence in the script.'

E.g. in this script, var and MyUninitialisedVar are created by their presence in the script, as shown by ListVars.

Code: Select all

#Warn
ListVars
MsgBox
var := MyUninitialisedVar

Also, I might add something like:
'variables can be created at runtime by using a dynamic reference':
e.g. varname := "myvar", %varname% := 1
I.e. varname is created at loadtime, and initialised (given a value) when you when run that line.
myvar is created and initialised when you run that line.

Furthermore, I might add something like:
'a variable is initialised by assigning a value to it'

TESTS

Some tests on VarSetCapacity:

Code: Select all

;test VarSetCapacity (tested on AHK Unicode)

a := {}
;b (no variable defined)
c := ""
VarSetCapacity(c, 0)
d := 0 + 0
e := 0
f := 0
f := 0 + 0
g := 1
g := !g
h := false ? 1 : 0
i := false ? 1 : !1
j := "0"
k := "abc"
l := "abcd"

MsgBox(VarSetCapacity(a)) ;AHK v1/v2: 0
MsgBox(VarSetCapacity(b)) ;AHK v1/v2: 0
MsgBox(VarSetCapacity(c)) ;AHK v1/v2: 0
MsgBox(VarSetCapacity(d)) ;AHK v1: 0, AHK v2: 6
MsgBox(VarSetCapacity(e)) ;AHK v1/v2: 6
MsgBox(VarSetCapacity(f)) ;AHK v1/v2: 6
MsgBox(VarSetCapacity(g)) ;AHK v1: 0, AHK v2: 6
MsgBox(VarSetCapacity(h)) ;AHK v1/v2: 6
MsgBox(VarSetCapacity(i)) ;AHK v1: 0, AHK v2: 6
MsgBox(VarSetCapacity(j)) ;AHK v1/v2: 6
MsgBox(VarSetCapacity(k)) ;AHK v1/v2: 6
MsgBox(VarSetCapacity(l)) ;AHK v1/v2: 14

Is your 'varexist' function an (reasonably successful) attempt at humour? It creates the variable if it doesn't exist, and thus always returns true.

Code: Select all

;test varexist

a := {}
c := ""
VarSetCapacity(c, 0)
d := 0 + 0
e := 0
f := 0
f := 0 + 0

MsgBox(varexist(a)) ;AHK v1/v2: 1
MsgBox(varexist(b)) ;AHK v1/v2: 1
MsgBox(varexist(c)) ;AHK v1/v2: 1
MsgBox(varexist(d)) ;AHK v1/v2: 1
MsgBox(varexist(e)) ;AHK v1/v2: 1
MsgBox(varexist(f)) ;AHK v1/v2: 1
;MsgBox(varexist("abc")) ;AHK v1/v2: error

varexist(byref var){
	if isbyref(var)
		return true
	throw ; invalid use
}

Do you have any further details re. your 'f()' example, it's quite counterintuitive. It's a great example, thanks for sharing.

Re: VarExist or static for global variables

Posted: 10 Jun 2019, 23:29
by jeeswg
I wrote some C++ code for a potential VarIsInit function, here:
C++: AHK source code: VarIsInit - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=75&t=65308

[EDIT:] This has since been implemented as IsSet:
IsSet - Syntax & Usage | AutoHotkey v2
https://lexikos.github.io/v2/docs/commands/IsSet.htm