[Wish] Wish to use 'Local' to prevent a nested function becoming a closure or introduce keyword 'Closure' to force it. Topic is solved

Discuss the future of the AutoHotkey language
QiuDao
Posts: 18
Joined: 25 Mar 2021, 22:55

[Wish] Wish to use 'Local' to prevent a nested function becoming a closure or introduce keyword 'Closure' to force it.

Post by QiuDao » 22 May 2021, 06:33

@lexikos Wish to use Local to prevent a nested function from becoming a closure, or to introduce the keyword Closure to force a nested function to become a closure.
For example, when writing a hook using Dllcall in an object method, to ensure the object instance can be released even if the hook has been installed and hasn't been unhooked yet, you should write code in a nested function rather than a closure as the callback function which is used for registering to the hook. You must not write the code in a method as the callback because a method must have captured this causing the object can not be released. You had better write the code in nested function carefully to avoid the callback becoming closure, just for safety purpose or for reducing unnecessary closure creation.
All in all, you sometimes really need simply write a nested function without being a closure, for a callback or some other purposes.
Without Local or Closure, you would probably write:

Code: Select all

class MouseHook {
	__New(Event := unset) {
		a:=1
		,b:=2
		,c:=3
		callBack(nCode, wParam, lParam) {
			local a:=5
			;...
			local b:=6
			;...
			local c:=7
		}
		cbk:=CallbackCreate(callBack, 'F')
		;...
		Closure1(){
			a:=5
			,b:=6
			,c:=7
		}
	}
	;...
}
m:=MouseHook()
With Local, you can write:

Code: Select all

class MouseHook {
	__New(Event := unset) {
		a:=1
		,b:=2
		,c:=3
		callBack(nCode, wParam, lParam) {
			local  ;use this keyword to avoid it becoming a closure.
			a:=5
			;...
			,b:=6
			;...
			,c:=7
		}
		cbk:=CallbackCreate(callBack, 'F')
		;...
		Closure1(){
			a:=5
			,b:=6
			,c:=7
		}
	}
	;...
}
m:=MouseHook()
Like force-local mode, using keyword local to make sure the nested function will never be a closure.
Or, with the new keyword closure, you can write:

Code: Select all

class MouseHook {
	__New(Event := unset) {
		a:=1
		,b:=2
		,c:=3
		callBack(nCode, wParam, lParam) {
			a:=5
			;...
			,b:=6
			;...
			,c:=7
		}
		cbk:=CallbackCreate(callBack, 'F')
		;...
		Closure1(){
			closure  ;use this keyword to make it become a closure.
			a:=5
			,b:=6
			,c:=7
		}
	}
	;...
}
m:=MouseHook()
I strongly recommend introducing the new keyword closure rather than using local. The code of an outer function can be copied or cut into another function to become a unclosure inner function without any modifications. The nested function becomes a closure only when the keyword closure is given in the first line of or in front of the function (note: an AHK developer would never write the closure keyword if there are no outer variables being captured or no need to become a closure). It obviously simplifies the rules and probably increases the performance, because it no longer needs to make complex rules to take time to determine whether a nested function should become a closure. All the program has to do is to check if the keyword closure is given.
With the introduction of the keyword closure, you can even change the V2 language to allow using %String%:= in a closure to modify and capture the value of a variable from an outer function without all the possible outer variables from %String% being captured in advance. It can not do this at present from the new documention.
Non-static local variables of the outer function cannot be accessed dynamically unless they have been captured.
https lexikos.github.io /v2/docs/Functions.htm#nested Broken Link for safety

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

Re: [Wish] Wish to use 'Local' to prevent a nested function becoming a closure or introduce keyword 'Closure' to force i  Topic is solved

Post by lexikos » 22 May 2021, 20:26

My primary reason for adding nested functions was to enable closures, and they were designed with that in mind. Fat arrow functions are usually closures, and do not have the capability to contain declarations. Full function definitions, fat arrow functions and #HotIf (which is like a fat arrow function) intentionally have the same behaviour, which means defaulting to allowing the capture of outer variables.

Copy-pasting a global function into another function is not something that I intend to support, although I considered making outer local variables more consistent with global variables. Global variables can be read by default without declaration, but cannot be assigned. If outer local variables were the same, they would still be captured if they are not assigned in the nested function, which would still become a closure. This didn't seem useful. On top of that, it would have required a new keyword since global doesn't really fit, and that might have defeated the point unless global is also replaced with this new keyword.

In the end, I chose to leave the local and global scopes as distinct concepts. There is some use in a nested function being able to declare global variables that weren't declared (and therefore weren't accessible) in the outer function.

If I reintroduced local, it would not be like force-local, it would be force-local. If the function does not resolve variables from the outer scope, it should not resolve global variables. That would include all global functions.

With closure, the program still needs to determine which variables are to be captured. It does not simplify the rules from the program's perspective; just adds another condition for the capture of variables. For the person reading the script, it does clarify what is or is not a closure.

Performance is a non-issue because the determination of Closure vs. Func is made at load-time. Variables are resolved in a specific order, and if a nested function resolves (captures) non-static local variables from an outer function, it becomes a closure.

A closure cannot modify non-static outer variables that it has not captured, because such variables only exist for the current instance of the outer function. There might not be a current instance, or it might be the wrong one. The only alternative is to capture all variables, which increases overhead and potentially creates more resource management problems. Specifically marking the function as a closure does not help - we already determine at load time whether or not it is a closure.

Aside from that, the capabilities of dynamic variable references will not have much weight on any of my decisions. The use of dynamic variables inhibits (or is inhibited by) detection of possible errors (#Warn VarUnset), and they are rarely the only good solution to a problem.

Finally, I'm not looking to reconsider any of my previous decisions or implement any new ideas at this point, with v2.0-beta.1 just around the corner.

Post Reply

Return to “AutoHotkey Development”