with Expression [ as Target ] { BLOCK }

Propose new features and changes
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

with Expression [ as Target ] { BLOCK }

28 Sep 2015, 10:24

No harm in trying..

Basic use case:

Code: Select all

with A_DetectHiddenWindows as "On" {
	; some commands here ...
	; within this block, A_DetectHiddenWindows is "On"
	; it is then resored to its previous value after
	; the block.
}
This will come in handy when using the ff built-in vars: A_IsCritical, A_BatchLines, A_TitleMatchMode, A_TitleMatchModeSpeed, A_DetectHiddenWindows, A_DetectHiddenText, A_AutoTrim, A_StringCaseSense, A_FileEncoding, A_FormatInteger, A_FormatFloat, A_KeyDelay, A_WinDelay, A_ControlDelay, A_MouseDelay, A_DefaultMouseSpeed, A_RegView

Slightly advance use case(inspired by Python, even the wordings <lame>):
The with statement may be used to wrap the execution of a block with methods defined by a context manager. A context manager(user-defined object) handles the entry into, and exit from, the desired runtime context for the execution of the block of code. (This is similar to how the for-loop can be customized via custom enumerators.)
The execution of the with statement may proceed as follows:
  • Expression is evaluated to obtain a context manager
  • The context manager's "enter"(may be __ New or something new like __Enter) method is invoked
  • If Target was included in the with statement, the return value from the "enter" method is assigned to it
  • BLOCK is executed
  • The context manager's "exit"(may be __ Delete or something new like __Exit) method is invoked. If an exception caused the BLOCK to be exited, perhaps an exception object may be passed to the "exit" method(__Delete can't be used in this case). If the block was exited due to an exception and the "exit" method returns false the exception is reraised, otherwise, if true, the exception is supressed and execution continues with the statement following the with statement.
** This allows common try... catch... finally.. usage to be encapsulated for convenient reuse

Code: Select all

with ContextManager as target {
	; some action
	target.SomeProperty ; 
}
;...
return

class ContextManager
{
	__Enter()
	{
		; Do something "important" like load resources, open a file, etc.
		return value_to_assign_to_target
	}

	__Exit(e)
	{
		if e { ; an Exception occured
			if (e.What == SOME_UNIMPORTANT_ERROR)
				return true
			else if (e.What == SOME_CRITICAL_ERROR)
				return false ; reraise
		}
		
		; Free resources etc.
		
		return true
	}
}
Summary:
Typical use include saving and restoring various global states, allocating and freeing resources, closing files, handles etc.
lexikos
Posts: 6214
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: with Expression [ as Target ] { BLOCK }

28 Sep 2015, 16:40

One is essentially variablename as temporaryvalue while the other is temporaryvalue as variablename. I don't think the first one makes sense grammatically, and the second one is like an assignment in reverse order, which isn't very intuitive. But mixing them is worse.

The second one is basically the same as C# using (var target = ContextManager) { ... }. You would "enter" the context when you construct the ContextManager and "exit" the context when ContextManager.Dispose() is called.

Since AutoHotkey uses reference counting and deterministic garbage collection (for lack of a better description), block-local variables could serve the same purpose.

Code: Select all

; #1
using (target := new ContextManager)
{
    target.SomeProperty
}
; #2
{
    let target := new ContextManager 
    target.SomeProperty ; 
}
return

class ContextManager
{
	__New()
	{
		; Do something "important" like load resources, open a file, etc.
	}
 
	__Delete()
	{
		; Free resources etc.
	}
}
It is probably more convenient to "enter" the context from __New, where you have access to parameters.

Although Dispose/__Delete wouldn't handle exceptions, they would be called when the object is freed even if an exception was thrown.
allocating and freeing resources, closing files, handles etc.
That's what constructors and destructors are for, though with a different method of garbage collection, having an explicit "scope" for the lifetime of the resource might be more useful.
guest3456
Posts: 2499
Joined: 09 Oct 2013, 10:31

Re: with Expression [ as Target ] { BLOCK }

28 Sep 2015, 17:23

for the block local variable, i think the using construct is more intuitive than with..as

lexikos
Posts: 6214
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: with Expression [ as Target ] { BLOCK }

28 Sep 2015, 21:42

My post was a bit unclear; "block-local variables" was referring to #2 (some form of declaration where the variable is scoped to the block which contains it, not the function), whereas #1 was an imitation of C#'s using, and would do more than just make the variable local to the block.

Regardless of the block local variable, I think the using construct is more intuitive than with..as.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: with Expression [ as Target ] { BLOCK }

29 Sep 2015, 10:39

lexikos wrote:One is essentially variablename as temporaryvalue while the other is temporaryvalue as variablename.
Yeah, I realized that both are actually the counterpart of the other. The with variablename as temporaryvalue example/demo was catered for casual usage. However, I have to admit that I was gunning for the second use case which, IMO, is more powerful due customizability and reusability, and it can also do what the first use case is trying to show.
lexikos wrote:The second one is basically the same as C# using (var target = ContextManager) { ... }. You would "enter" the context when you construct the ContextManager and "exit" the context when ContextManager.Dispose() is called.
Cool, I wasn't aware that the using keyword can be used to create using statements to handle Dispose/IDisposable except of course for the more commonly seen using Namespace directive. Having said that, I have to agree that using is definitely better than with ... as ... - it's more AHK-esque.
Obviously, #2(C# imitation) is the more attractive(IMO) proposition. ContextManagers/"IDisposable"-ish objects are easy to implement and can be reused throughout multiple projects. Here's a simple demo to manage global states/settings:

Code: Select all

using (dhw := new DetectHiddenWindows("On")) {
	; do something here
}

using (wd := new TempWorkingDir("C:\Foo")) {
	; do something here
	wd.Value := "C:\AnotherDir" ; caller is at liberty to change working dir, the original working dir will always be restored
	; some code
}


class DetectHiddenWindows extends VarRestorer
{
	__New(OnOrOff)
	{
		base.__New(OnOrOff, A_DetectHiddenWindows) ; caveat - class(es) extending from VarRestorer must explicitly cast to VarRestorer.__New()
	}

	Value[]
	{
		set {
			DetectHiddenWindows, %value%
			return base.Value := value
		}
	}
}

class TempWorkingDir extends VarRestorer
{
	__New(TempDir)
	{
		base.__New(TempDir, A_WorkingDir)
	}

	Value[]
	{
		set {
			SetWorkingDir, %value%
			return base.Value := value ; same caveat, must cast to VarResoter.Value.set
		}
	}
}

class VarRestorer
{
	__New(NewValue, OldValue)
	{
		this.Prev := OldValue
		this.Value := NewValue
	}

	__Delete()
	{
		this.Value := this.Prev
	}

	Value[]
	{
		get {
			return this._Value
		}
		set {
			return this._Value := value
		}
	}
}
Obviously the code above can be performed(now) by using try .. finally ...(as shown below) except that using is clearer on its intent of having an explicit scope for the lifetime of whatever the "ContextManager" is trying to manage plus of course the advantage of "block-local variables":

Code: Select all

try {
	dhw := new DetectHiddenWindows("On")
	MsgBox, % WinExist("ahk_id " . A_ScriptHwnd)
}
finally
	dhw := ""
MsgBox, %A_DetectHiddenWindows%
lexikos
Posts: 6214
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: with Expression [ as Target ] { BLOCK }

10 Mar 2016, 19:01

There are couple more ways to emulate it:

Code: Select all

WinExist("A")
MsgBox % LastFoundWindowTitle()
for x in new LastFoundWindow("ahk_class Progman")
	MsgBox % LastFoundWindowTitle()
MsgBox % LastFoundWindowTitle()
	
LastFoundWindowTitle() {
	WinGetTitle title
	return title
}

class LastFoundWindow extends UsingContext {
	__New(p*) {
		this.old := WinExist()
		WinExist(p*)
	}
	__Delete() {
		WinExist("ahk_id " this.old)
	}
}

class UsingContext {
	_NewEnum() {
		return this
	}
	Next() {
		return this.ran ? false : (this.ran := true)
	}
	/* ; alternative:
	Next() {
		this.Next := this.Stop
		return true
	}
	Stop() {
		return false
	}
	*/
}
x is a dummy variable. You could set it to whatever you want within Next().

You can also utilize the default enumerator:

Code: Select all

WinExist("A")
MsgBox % LastFoundWindowTitle()
for x in [new LastFoundWindow("ahk_class Progman")]
	MsgBox % LastFoundWindowTitle()
MsgBox % LastFoundWindowTitle()
	
LastFoundWindowTitle() {
	WinGetTitle title
	return title
}

class LastFoundWindow {
	__New(p*) {
		this.old := WinExist()
		WinExist(p*)
	}
	__Delete() {
		WinExist("ahk_id " this.old)
	}
}
The enumerator keeps a reference to the array, which contains the LastFoundWindow object. If you specify a second variable, it will receive a reference to the object, which will prevent it from working in v1 (since v1 doesn't reset the variable when the loop terminates).

Unfortunately there's a bug affecting both of these: when __delete is called after an exception is thrown but before it is handled, the exception is not cleared. For v1, this means that the next call to a built-in function (or array/member access) will cause it to be re-thrown. Using try/catch/finally within __delete will cause inconsistent results. [Edit: v1.1.23.03 fixes the bug.]

There's also a bug in v2 that prevents the second method from working at all, but after trying to figure out what was going on, I realized I've already committed a fix (but not released an update). [Edit: No, even worse, I had released an update but not been using it myself on either of the computers I tested on.] :facepalm:
Last edited by lexikos on 11 Mar 2016, 21:48, edited 1 time in total.
Reason: See above

Return to “Wish List”

Who is online

Users browsing this forum: No registered users and 1 guest