[Wish]: Allow Break Outside of a Loop, Such as In a Function

Discuss the future of the AutoHotkey language
rowsinator
Posts: 6
Joined: 18 Oct 2023, 19:48

[Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by rowsinator » 18 Oct 2023, 21:00

Hi, for whatever reason, "break" cannot be put inside a function or a label, or really anywhere that isn't nested inside a loop, as per the documentation. You can't even do it if that function is inside a loop. I write Powershell scripts professionally, and use functions to nest my code, make it easier to write, and reduce total lines of code. I'll often have a function wrap something up or prepare something for later before I break a loop. As of now, functions cannot be used to break loops, because my script won't start.

Here is a basic example of what I want to do but can't. Start the loop with F7 and use ESC to trigger the function in the loop. I've commented out the "break", but ideally, "break" should replace the "exit". Exit does not have the functionality I need:

Code: Select all

; ESCAPE SEQUENCE---------------------------->
ToolTip "init"
ExitVar := 0
Esc:: {
    global
    ;MsgBox "esc pressed"
    ExitVar := 1

}

LoopExiter(ExitVar){ 
    if(ExitVar = 1){
        ExitVar := 0
        MsgBox ExitVar " FROM FUNCTION"
        Exit ;Comment this out and use break for example
        ;Break
    }

}
; ESCAPE SEQUENCE---------------------------->


F7::{
    global
    Exitvar := 0
    LoopNumber := 0
    loop 10000000{
        LoopExiter(ExitVar)
        Tooltip LoopNumber += 1
        ;Sleep 1000
    }
    
}
In powershell this is trivial to do:

Code: Select all

function BreakFunction {
    param (
        $i
    )
    $a = 1
    $b = 2
    $Script:GetOut = "Out"
    Write-host "    I am doing other things inside the function before I use `"break`""
    Write-host "    I am passing a variable to the function, `$i:" $i
    Write-host "    I have defined two variables inside the function, `$a(1) and `$b(2), and can add them together here:" ($a+$b)
    Write-host "    I am making a variable available to the rest of the script from the function: `"`$GetOut`""
    break
}
for ($i = 0; $i -lt 999999999; $i++) {
    Write-Host "Loop iteration $i"
    if ($i -ge 1000) {
        Write-Host "This script will now break using the function `"BreakFunction`""
        Write-Host ""
        BreakFunction -i $i
    }
}

    Write-host ""
    Write-host ""
    Write-host "I have now broken out of the loop and functioning as normal"
    Write-host "Variable from `"BreakFunction`": $GetOut"
Right now break isn't as "portable" as it should be, I should be able to break out of a loop no matter where I am, even if I'm calling "break" from a function. I can't even use GOTO for functionality similar to my Powershell example. I feel as though I should get an error at runtime when the script attempts to "break" something and it's not in a loop, instead of my script not launching at all.

User avatar
emmanuel d
Posts: 97
Joined: 17 Nov 2013, 04:45

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by emmanuel d » 19 Oct 2023, 00:45

There were many times i wanted to break out of a If statement.
But in a function u use return to break out of it.
use a proper result, like 0 or a true value in your return.
If the function returns 0 break out of the loop, else keep going, that is easy and verry readable.

Code: Select all

Loop  {                                           ; Infinit loop
  If !SomeFunction(A_Index){                      ; If not sucsesfull
    MsgBox("Failed on: " A_Index, A_ThisFunc, 262144)
    Break
    }
  MsgBox("A OK for: " A_Index, A_ThisFunc, 262144)
  }
SomeFunction(L_Nr){
  If L_Nr = 5                                     ; We want to fail on nr 5
    Return 0                                      ; Return Failure
  Else Return L_Nr                                ; Return All is OK
  }
in case of the "If", you put all the code in a loop:

Code: Select all

ITest := 5
If Test
  Loop 1 {                                        ; Everything that would go in the If needs to be in the loop
    MsgBox("This is always shown", A_ThisFunc, 262144)
    If Test = 5                                   ; The condition to exit the if
      Break
    MsgBox("This is never executed", A_ThisFunc, 262144)
    }

User avatar
emmanuel d
Posts: 97
Joined: 17 Nov 2013, 04:45

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by emmanuel d » 19 Oct 2023, 01:00

In your case exitvar is always 0
and esc is never executed, due to autohotkey is single threaded.
you have a msgbox there so you know that while F7 runs, that msgbox will never be executed

see next post.
Last edited by emmanuel d on 19 Oct 2023, 01:31, edited 2 times in total.

User avatar
emmanuel d
Posts: 97
Joined: 17 Nov 2013, 04:45

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by emmanuel d » 19 Oct 2023, 01:30

I was mistaken Esc was blocked by my text editor, this works as expected:

Code: Select all

#Requires AutoHotkey v2.0
#SingleInstance force   
; ESCAPE SEQUENCE---------------------------->
ToolTip "init"
ExitVar := 0
Esc:: { 
  global
  MsgBox "esc pressed"
  ExitVar := 1
  }
LoopExiter(ExitVar){ 
  if(ExitVar = 1){
    ExitVar := 0
    MsgBox ExitVar " FROM FUNCTION"
    Return 0
    }
  Return 1
  }
; ESCAPE SEQUENCE---------------------------->
F7:: {
  global
  Exitvar    := 0
  LoopNumber := 0
  loop 10000000 {
    If !LoopExiter(ExitVar)
      Break
    Tooltip LoopNumber += 1
    }
  }

rowsinator
Posts: 6
Joined: 18 Oct 2023, 19:48

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by rowsinator » 19 Oct 2023, 08:20

I do appreciate the help, AHK is not my most proficient language.

I whipped up that working AHK example to explain the functionality I'm used to and find useful. As of now, I don't really understand why I get a, lets say, "compile error" when I don't have my "break" directly inside a loop. When used correctly, I believe the break inside my function should work as expected. I feel as though I should get an error when it is used wrong when running the script, not at "compile time"

neogna2
Posts: 619
Joined: 15 Sep 2016, 15:44

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by neogna2 » 19 Oct 2023, 16:33

I'm not sure I see a net benefit with your suggestion compared to the current behaviour. Can you explain more why you think the suggested change would improve things?

rowsinator
Posts: 6
Joined: 18 Oct 2023, 19:48

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by rowsinator » 19 Oct 2023, 20:44

neogna2 wrote:
19 Oct 2023, 16:33
I'm not sure I see a net benefit with your suggestion compared to the current behaviour. Can you explain more why you think the suggested change would improve things?
Yes, I would be able to call "break" whenever I need to be deterministic about when I "break", but in a function that can do other things before the "break". I'm not asking for there to be no error when you actually try to "break" improperly, especially if AHK has some harmful functionality when you "break" outside of a loop. But I don't really see the value in not letting you "break" whenever you want. I see only downsides and no benefits to not letting you use "break" wherever you want. The downsides include:
* Code bloat because "break" can't be used in functions
* Code bloat because "break" can't be used in GoTo

I can understand GoTo, but why also functions? By allowing you to put "break" in a function, you gain the benefit of code reuse (I guess Goto too technically, but that's its own discussion).

I really dislike rewriting code when I can just use a function, because that's what functions are for. If I so choose, I should absolutely be able to call "break" within a function that only calls "break". And it should absolutely blow up in my face if I use "break" improperly when I call it improperly during the runtime of my script, especially if there is harmful functionality in AHK. I haven't been following the development of AHK v2, but I feel like this decision is one where AHK is trying to stop the user from making mistakes when writing code, as opposed to actually stopping the user from doing something harmful when running code. This is something you point out could be a problem if you aren't careful in an editor, not at the scripts runtime, that also stops it from functioning at all.

For my Powershell scripts, most of my loops are not endless. They run for a certain amount of cycles depending on certain criteria. I use "break" conditionally when certain criteria are met that will cause problems in the current loop, but also want the script to keep running and finishing off other things. "Break" goes into a function that accomplishes those things for me. As an example example, in a "for-each" loop that needs to bail early - "say where in the list you stopped early because a condition was met, and write to a file. Then break the loop". A function can be reused, so you will see that kind of function inside a lot of my loops. I'd love to show you a real example in Powershell, but the work I do for my company is proprietary, and I don't feel comfortable copy pasting something with a few variables changed.

Finally, in Powershell, "break" does nothing if you aren't already in a loop (picture for reference bellow). You can test this yourself by pulling up a Powershell window (Powershell 7 is cross platform if you are not on windows and are curious), type "break" and hit enter. You don't even get an error, so I don't see having extra "breaks" that do nothing as being harmful. Are "breaks" harmful in AHK v2 when not inside a loop? I think that if "break" is harmful, it should be made benign instead, though I don't know what that would entail.
Windows Terminal Break Example.png
Windows Terminal Break Example.png (9.99 KiB) Viewed 4969 times

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

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by lexikos » 19 Oct 2023, 21:40

How many and which statements is it supposed to break out of? What if there is both an If and a Loop?

If you really need to "break out of" a group of statements that aren't enclosed by a loop, it may be that extracting that code into a separate function would be a better course of action. Otherwise, this is a valid use of goto.

A function returns a value, not a control flow instruction. Code would be harder to follow if any function call could perform a break. (Exceptions are similar, but are intended for error/exceptional conditions, and you know that either the thread will exit or execution will jump to try/catch/finally.)

If you want reusable code, you can surely write it in a way that makes conventional use of control flow structures. There is no need for a function to act as as a substitute for break. For instance, put the loop into a reusable function. As a bonus, a single call to a function which performs a loop is more efficient than a loop which calls a function for every iteration.

Perhaps you can explain what the alternative to "break can't be used in GoTo" would be. Both Break and Goto are statements which transfer control to a specific line. Identifying the line after a loop with a label and using Goto is exactly equivalent to using Break. Suggesting that one might be used "in" another makes no sense to me.

In both cases, the target line is identified at "compile time" (at load time, really).
AHK is trying to stop the user from making mistakes when writing code
Break aside, yes, v2 tries to stop the user from making mistakes when writing code. This is much more effective than relying solely on runtime errors. The specific conditions for detecting an error at runtime might not be met during testing, or reproducing those conditions might require going through a number of steps, making testing a much slower process. If an error can be detected before the code executes, that is much better.

Editors are able to detect errors even earlier than AutoHotkey itself, but only if the conditions actually indicate an error in the first place. If the language permits Break to be used (meaningfully) anywhere, the use of Break can't be validated by editors any more than by the program itself.

rowsinator
Posts: 6
Joined: 18 Oct 2023, 19:48

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by rowsinator » 20 Oct 2023, 10:27

Sorry for the picture in my last post. I'm not sure what happened. It was just a picture of powershell opened in a terminal, and a break command being sent. Also I'm not familiar with this forum software, or formatting etiquette so please bare with me.

lexikos wrote:
19 Oct 2023, 21:40
How many and which statements is it supposed to break out of? What if there is both an If and a Loop?
I'm a bit confused by this. You cannot use break in a pure if statement that is not nested in a loop, so I don't think that matters. When you do, you get an error when you try to run the script. As for how many loops I want to break out of, it's only supposed to break the loop I put the function in, whatever "layer" that is. As per my first example, the function inside the loop runs every "tick", perhaps this is not the most resource efficient. The function itself is waiting for the ESC key to change a variable that will trigger the if statement inside the function. Ideally, when this if statement is triggered from inside the function, and is nested inside the loop, it should break the loop it's in, and only the loop it's in. I'm not asking for fancier functionality, though I know from the documentation you can do other fancy stuff with break.

lexikos wrote:
19 Oct 2023, 21:40
If you really need to "break out of" a group of statements that aren't enclosed by a loop, it may be that extracting that code into a separate function would be a better course of action. Otherwise, this is a valid use of goto.
I am not trying to break out of anything but a single loop. As of now, you have to do this in order to achieve the same result as putting break in a function, which is not as reusable:

Code: Select all


FunctionExample02(){
    ToolTip "ACTIVATED"
	;Other things can go here.
	;Other things can go here.
	;Other things can go here.
}

Test = 1

F6::{
    
    loop 10{
    	;-------- I have to rewrite this part every time
        ;I have to make this if statement every time when I could just put the if statement in the function if I could put [c]break[/c] wherever I want. This more Lines of Code.
        if (Test = 1){
            FunctionExample02
            break
        }
        ;-------- I have to rewrite this part every time
    }
}




In my first example, the point of my loop is to run continuously and break when I press ESC. Maybe there is a better way to do this, but I think the example is functional. I can make this a snippet, but I would prefer not to have to. This is also more lines of code.

lexikos wrote:
19 Oct 2023, 21:40
A function returns a value, not a control flow instruction. Code would be harder to follow if any function call could perform a break. (Exceptions are similar, but are intended for error/exceptional conditions, and you know that either the thread will exit or execution will jump to try/catch/finally.)
Powershell does this and I can't say it's really "confusing". My Powershell example in my first post works exactly as you think it would. You enter a loop and then when the function is called, it does everything in the function; it runs all the code, including break, and then the script continues after the loop. If you don't know what the function does when looking through your code, you can just go to the function and read it.

lexikos wrote:
19 Oct 2023, 21:40
If you want reusable code, you can surely write it in a way that makes conventional use of control flow structures. There is no need for a function to act as as a substitute for break.
This is your opinion. I often use functions as a part of a "control flow structure" as you put it. If this is about code correctness, when written properly, my code does the correct thing, and produces no errors. I am not asking to only use break in a function, I am asking for the ability for a function to be able to preform a break, plus other run other code in the function before it.

lexikos wrote:
19 Oct 2023, 21:40
For instance, put the loop into a reusable function. As a bonus, a single call to a function which performs a loop is more efficient than a loop which calls a function for every iteration.
This does not solve my problem. I am looking for the break functionality to be portable, not the entire loop. I want to do a specific set of things before I break, and not allowing me to put it inside a function adds extra complexity to my code. I have to look in more than one place when I want to make an edit. I used a finite loop as an example so that it doesn't actually run forever, but the loops I am looking to break do. The thing I am trying to avoid typing over and over again is:

Code: Select all

    if(ExitVar = 1){
        ExitVar := 0
		FunctionExample()
        ;MsgBox ExitVar " FROM FUNCTION"
        Break
    }

I'm not trying to put break in a function because I think it's cool to call functions. The whole point is to avoid having to write extra code. I have this flexibility in Powershell and think it is very powerful and versatile. I think AHK v2 should have that too.

lexikos wrote:
19 Oct 2023, 21:40
Perhaps you can explain what the alternative to "break can't be used in GoTo" would be. Both Break and Goto are statements which transfer control to a specific line. Identifying the line after a loop with a label and using Goto is exactly equivalent to using Break. Suggesting that one might be used "in" another makes no sense to me.
You are correct. This is my mistake.

lexikos wrote:
19 Oct 2023, 21:40
In both cases, the target line is identified at "compile time" (at load time, really).
I think "load time" is the proper statement here. You are correct, my mistake.

lexikos wrote:
19 Oct 2023, 21:40
AHK is trying to stop the user from making mistakes when writing code
Break aside, yes, v2 tries to stop the user from making mistakes when writing code. This is much more effective than relying solely on runtime errors.
I agree to some degree, but I feel that this is particularly egregious when it comes to break. If break is not harmful, the user should be able to put break anywhere they want. I'm aware this is not "correct" or "useful" so to speak. I think that performance and "code correctness" matter. I also agree that relying too heavily on functions can be very confusing, and make your code harder to follow, and not the other way around. But I think, in terms of break's functionality, this is going too far, and reduces the capability of AHK v2. I can understand if this caused blue screens or make your entire script fail, but so far, I haven't really seen evidence of something that catastrophic.

To say it more simply, I don't think that using break in this way is incorrect.

lexikos wrote:
19 Oct 2023, 21:40
The specific conditions for detecting an error at runtime might not be met during testing, or reproducing those conditions might require going through a number of steps, making testing a much slower process. If an error can be detected before the code executes, that is much better.

Editors are able to detect errors even earlier than AutoHotkey itself, but only if the conditions actually indicate an error in the first place. If the language permits Break to be used (meaningfully) anywhere, the use of Break can't be validated by editors any more than by the program itself.
While I don't disagree in theory, AHK can already tell when break is used outside of a loop. Can we not use this same functionality to warn people in an editor based on the same criteria? For example this would be a yellow line in VSCode, which is what I'm using. Trying to use variables you haven't assigned produces the effect I'm thinking about. I feel like that would be a much better way to tell the user "hey, you might be using this wrong" than a hard fail at load time.

neogna2
Posts: 619
Joined: 15 Sep 2016, 15:44

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by neogna2 » 20 Oct 2023, 12:48

rowsinator wrote:
20 Oct 2023, 10:27

Code: Select all

;I have to make this if statement every time when I could just put the if statement in the function if I could put [c]break[/c] ...
if (Test = 1){
    FunctionExample02
    break
}
As emmanuel d showed you can use return values from the function to mimic the break behaviour you want so you can already move the Test = 1 conditional into a function.
Compare what you want (currently throws loadtime error)

Code: Select all

loop
    MyFunc()
MsgBox "loop ended"

MyFunc() {
    if 1 = 1 ; or whatever test you want
        break
}
to what you currently can do

Code: Select all

loop
    if MyFunc()
        break
MsgBox "loop ended"

MyFunc() {
    if 1 = 1
        return 1
}

rowsinator
Posts: 6
Joined: 18 Oct 2023, 19:48

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by rowsinator » 20 Oct 2023, 13:37

@neogna2
Thank you, I do have some other logic that mimics the behavior I want. This is a personal project and not something that anyone is paying me for, so it's not "urgent that I get this fixed IMMEDIATELY", but I do appreciate the support a lot. I do not doubt that the current way I want to do this in AHK is "wrong" or at the very least, not supported.

As part of my desire, I'm curious to know what would happen if I could use break outside of a loop in AHK. (I know I actually can't, but I'm still curious). I've never had the issue of my Powershell scripts exiting early because of a break, so I did look it up in the official Microsoft documentation. Microsoft actually does tell you when you shouldn't use break in Powershell. (Basically when you use it outside loop like code structures).

https://learn.microsoft.com/en-us/power ... ch-or-trap

break actually does cause issues when used improperly, that is to say, when you use it outside of code blocks that support it, but if you use it properly you would never notice. The main issue is that it would end your entire script early if not properly encapsulated. While this may cause problems, you could consider this behavior a feature if implemented properly in a script. And I think this functionality could be considered a plus, not a minus.

I agree that finding errors before you run your script is extremely valuable, there is a reason I use VScode and not the standard Notepad. But I think that should be presented to the user in a different way than a not allowing the script to run at all. Unfortunately, I do not have any experience implementing editor features. I don't know how difficult it would be to have the editor know when you use break outside of a code structure that supports it, in this case a loop. I also don't know what unforeseen consequences could happen if you break outside a loop while a script is running. If all that would happen is the script exits, I think that should be ok.

geek
Posts: 1068
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by geek » 21 Oct 2023, 17:46

For what it's worth, AutoHotkey does have a flow control statement that allows you to, as the Powershell documentation puts it, "look up the call stack for an enclosing construct". (Also noting that Microsoft's same documentation page says "Do not use break outside of a loop, switch, or trap").

The statement is throw, and using throw allows you to break to the enclosing try block that has a matching catch and even pass values back up to wherever it broke from:

Code: Select all

#Requires AutoHotkey v2.0

; ESCAPE SEQUENCE---------------------------->
ToolTip "init"
ExitVar := 0
x:: {
    global
    MsgBox "esc pressed"
    ExitVar := 1

}

class CustomThrowable {
	__New(value) => this.value := value
}

LoopExiter(ExitVar, number){ 
	Tooltip number
    if(ExitVar = 1){
        ExitVar := 0
        MsgBox ExitVar " FROM FUNCTION"
        throw CustomThrowable(number)
    }

}
; ESCAPE SEQUENCE---------------------------->


F7::{
    global
    Exitvar := 0
    LoopNumber := 0
	try {
	    loop 10000000
	        LoopExiter(ExitVar, A_Index)
	} catch CustomThrowable as e {
		msgbox "exited with value " e.value
	}
}

User avatar
thqby
Posts: 565
Joined: 16 Apr 2021, 11:18
Contact:

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by thqby » 22 Oct 2023, 01:15

rowsinator wrote:
20 Oct 2023, 13:37
Unfortunately, I do not have any experience implementing editor features. I don't know how difficult it would be to have the editor know when you use break outside of a code structure that supports it, in this case a loop.
This feature has been implemented in vscode-autohoeky2-lsp v2.2.1

rowsinator
Posts: 6
Joined: 18 Oct 2023, 19:48

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by rowsinator » 24 Oct 2023, 08:25

thqby wrote:
22 Oct 2023, 01:15
This feature has been implemented in vscode-autohoeky2-lsp v2.2.1
I'm very happy about this.
VScode Break Warning.png
VScode Break Warning.png (4.93 KiB) Viewed 4352 times

I think that, since this is possible to achieve, the functionality of not allowing breaks outside loops should be changed, as the user can receive a significant warning in their editor. This improves functionality and flexibility of AHK v2. I see no reason to disallow the use of break in this way due to "code correctness".

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

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by lexikos » 16 Nov 2023, 19:00

I think it is strange that you can't see the conflict between allowing breaks outside loops and the editor highlighting them as errors. If one wants to use break "legitimately" outside of a loop, one has to put up with the error indicators or disable them, in which case one loses the benefit of error detection.
I wrote:Editors are able to detect errors even earlier than AutoHotkey itself, but only if the conditions actually indicate an error in the first place. If the language permits Break to be used (meaningfully) anywhere, the use of Break can't be validated by editors any more than by the program itself.
Most code is inside functions (including hotkeys, where the function name is omitted from the definition). Determining whether a function will be called from within a loop using static analysis would be much more complex than simply determining whether break is enclosed by a loop, and doing it with perfect accuracy is impossible.

Another side of
I wrote:A function returns a value, not a control flow instruction.
is that break can be, and in some languages is, implemented as a simple jump. The compiler knows where the target is, and some compilers can optimize for it. For a function to break a loop in some caller of the function, it must be implemented at runtime. This likely impacts performance or complexity, and imposes a constraint on future implementations of the language.

User avatar
DuckingQuack
Posts: 222
Joined: 20 Jan 2023, 18:20

Re: [Wish]: Allow Break Outside of a Loop, Such as In a Function

Post by DuckingQuack » 16 Nov 2023, 21:50

@rowsinator This probably will seem a bit useless for your applications, however, the functionality you are describing regarding being able to turn off a loop at will already exists in the form of SetTimer. Have it callback to a function that is the series of commands you want looped and set the time to 10ms with a simple toggle thrown in like SetTimer(Function, 10 * on) and use whatever means you want to on:=!on and BAM! An infinite loop that turns on and off when you need it to! Just don't forget to initialize the variable with on:=False.
Probably not the best advice, but I think its a decent option.
Best of Luck,
The Duck

Post Reply

Return to “AutoHotkey Development”