dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
288 stars 66 forks source link

Proposal: More control on exiting and continuing nested loops. #420

Closed VBAndCs closed 5 years ago

VBAndCs commented 5 years ago

Alias is a keyword used with API declare statement. I suggest to use it to optionally define a label name for (for, do, while) loops, to have more control over exit and continue statements, like this: Ex:

For i= 0 to 10 Alias=for1
    For j = 0 to 10
       ' some code.
      If …. Then Exit for1
     ' some code.
      If …. Then Continue for1
    next
Next

Note that for1 should be treated as var name, so it can't be repeated inside method scope. The above code can be lowered to:

For i= 0 to 10
    For j = 0 to 10
       ' some code.
      If …. Then Goto Exitfor1
     ' some code.
      If …. Then Goto Continuefor1
    Next

   Continuefor1:
Next
Exitfor1:

I implemented this idea in ZML 1.0 (repo - NuGet, but I don't have a keyword issue in xml tags, so I am using the label attribute. I think VB.NET can use #label as a keyword not to preserve the label word. It can even allow using #for1 directly without a keyword. Using # before new keywords spatially when they are optional, can make it easy to add new features to VB without worrying about breaking anything. Anyway, Alias is a good alternative. This is a ZML sample:

<z:foreach var="country" in="Model.Countries" label="loop1">
    <h1>Country: @country</h1>
    <z:foreach var="city" in="country.Cities">
        <z:if condition="city.StartsWith(''_'')">
            <z:then>
                <z:exit label="loop1"/>
            </z:then>
            <z:else>
                <z:exit/>
            </z:else>
        </z:if>
        <p>City: @city</p>
    </z:foreach>
</z:foreach>

this will generate

@foreach (var country in Model.Countries)
{
  <h1>Country: @country</h1>
  foreach (var city in country.Cities)
  {
    if (city.StartsWith("_"))
    {
      goto break_loop1;
    }
    else
    {
      break;
    }
    <p>City: @city</p>
  }
  continue_loop1:
}
break_loop1:
AdamSpeight2008 commented 5 years ago

Similar to #186

VBAndCs commented 5 years ago

@AdamSpeight2008 I am aware that this many asked for doing something about this issue, but my suggestion is different, and it is doable. The problem with using the counter as a label is that it can be repeated in the same method, so, it needs more work to insure that the label name is unique. So, I suggest to allow us to define our labels.

pricerc commented 5 years ago

@AdamSpeight2008 The problem with using the counter as a label is that it can be repeated in the same method, so, it needs more work to insure that the label name is unique

This doesn't make sense. The counter cannot be repeated within the loop, so there is no uniqueness problem.

If I adapt and compare the example in proposal #186 with your proposal:

' proposed in #186
For i As Integer = 1 To 2
    For j As Integer = 2 To 3
        If i = j Then
            Exit For i
        End If
        If SomeCondition(i) Then
            Continue i
        End If
    Next j
    PerformSomeTask()
Next i

' proposed in #420
For i As Integer = 1 To 2 Alias=for1
    For j As Integer = 2 To 3
        If i = j Then 
            Exit for1
        End If
        If SomeCondition(i) Then
            Continue for1
        End If
    Next j
    PerformSomeTask()
Next i

In compilation, both would convert to:

For i As Integer = 1 To 2
    For j As Integer = 2 To 3
        If i = j Then
            GoTo $Exit_i
        End If
        If SomeCondition(i) Then
            GoTo $Continue_i
        End If
        $Continue_j: ' unused in this example, because I'm lazy
    Next j
    $Exit_j: ' unused in this example, because I'm lazy
    PerformSomeTask()
    $Continue_i: 
Next i
$Exit_i:

I see no compiled difference, but:

  1. your proposal requires explicitly declaring the hidden labels, whereas the alternative just uses the existing counter to create the same hidden labels.
  2. with your proposal when I see Continue for1, I would have to go looking for for1 to find out which loop variable is involved, whereas with the other proposal, Continue i tells me exactly which loop is involved.
VBAndCs commented 5 years ago

@pricerc What about:

Sub Foo()
For i = 1 to 10
   exit i
next

For i = 0 to 5 
     exit i
next
End Sub

These two loops will generate the same labels, and need to add extra ID. I once poposed using the counter in C#, but when I am implementing ZML I founf it eaier to use the label. Besides, there is no counter in Do and While loops. My proposal covers that too.

pricerc commented 5 years ago

@pricerc What about:

Sub Foo()
For i = 1 to 10
   exit i
next

For i = 0 to 5 
     exit i
next
End Sub

These two loops will generate the same labels, and need to add extra ID.

No they won't:

Sub Foo()
  For i = 1 to 10
    GoTo $exit_i_1
 next
 $exit_i_1

 For i = 0 to 5 
      GoTo $exit_i_2
 next
 $exit_i_2
 End Sub

The labels would be actually be some mangled name, not something that would necessarily have a name that would be meaningful, I just used those for illustration. The compiler wouldn't be so dumb as to not know that it would need different labels.

So for For loops, I'm sorry but the other proposal is much better.

Besides, there is no counter in Do and While loops. My proposal covers that too.

There is no example of that in your proposal, so I can't really comment on it, but I'd be very curious to see a real-world example of some code that's in common use that would benefit the new feature that you're proposing.

pricerc commented 5 years ago

I decided to Google 'named while loop', and came across much the same discussion regarding Python.

The language proposal for "Labeled break and continue" is here

And is 'pretty much' the same discussion. Proposal 'A' in particular looks very much like this one.

The reason for its rejection explained here.

The reasons for the rejection are completely relevant to this proposal, because they transcend language:

However, I'm rejecting it on the basis that code so complicated to require this feature is very rare. In most cases there are existing work-arounds that produce clean code, for example using 'return'. While I'm sure there are some (rare) real cases where clarity of the code would suffer from a refactoring that makes it possible to use return, this is offset by two issues:

  1. The complexity added to the language, permanently. This affects not only all Python implementations, but also every source analysis tool, plus of course all documentation for the language.

  2. My expectation that the feature will be abused more than it will be used right, leading to a net decrease in code clarity (measured across all Python code written henceforth). Lazy programmers are everywhere, and before you know it you have an incredible mess on your hands of unintelligible code.

AdamSpeight2008 commented 5 years ago

@VBAndCs The other proposal has already been approved by LDM. The compiler (I think) lowers every loop construct to label ... GoTo label form , during which it automatically inserts unique labels for each stage. That proposal suggests that the compiler could use them with the Exit For i and Continue For i.
I suspect the changes need to support this form will a lot less than this proposal, less potential to break existing code.

AdamSpeight2008 commented 5 years ago

I may look at prototype the other proposal once I finish the one I currently working on.

VBAndCs commented 5 years ago

@pricerc These reasons are common to all new features, but did nor prevent them. In fact this proposal is more important in C# and alikes. Exit do, Exit while, exit for can resolve the ambiguity when the two nested loops are of different types. But still there is a chance to have two nested loops of the same type, so we use goto in this case. This difference between C# and VB forced me to use lables to allow both programmers to use ZML in the same way. In VB, we can make things better a littel if we add Exit For each! I also suggested before in C# to use some identifiers. In VB this can be like: Exit Parent, and Exit Root. You will find many requests in many languages to enhance this syntax. This doesn't makes sense after all these decades of programming and all new wonderfull features, to have to write goto just to control the flow of nested loops! There is also the issue of using a flag just to know if the loop ended normally or broken from! I may suggest also to add this flag to the label, to be used like this: If for1.EndedNormally Then BASIC will be 55 years old in the next week. It is too long time to keep writing all these small workarounds over and over. If you think you are used to it, conseder the new generations with all tech they are playing with now, how will they respond to such focil syntax with the argument it is too much to fix that now! A new generation worth to have a modern VB.

AdamSpeight2008 commented 5 years ago

I don't think we are suggesting the explicit use of GoTo, just the compiler hide it usage behind a syntactic screen.

VBAndCs commented 5 years ago

I was not talking about the other proposal. I was talking of the workaround that we use when we have two loops of the same type so Exit 'type' applies only to the inner loop.

AdamSpeight2008 commented 5 years ago

Exit For already does that, the loop identifier is optional.

VBAndCs commented 5 years ago

And this will be the case with two nested do loop. How can you exit the outer loop while there is no counter? My solution:

Do while cond1 Alias = Loop1
     Do
           If Cond2 Then Exit Do
            If Cond3 Then Exit Loop1
     Loop
Loop
AdamSpeight2008 commented 5 years ago

The other proposal is currently restricted to For, ForEach as they have "labels" that can be used. Other forms for loop don't require to have one. In those case I probably be explicit in my intent and use well described labels and goto. Or rewrite that section in a different form.

Do While cond1
  Do
    If cond2 Then Exit Do
    If cond3 Then GoTo exit_loop1
  Loop
Loop
exit_loop1:
VBAndCs commented 5 years ago

Another thought: define a Loop object, with methods to control the loops, like: Loop.Exit( ) and Loop.Continue( ) Loop.ExitOuter(level as integer) and Loop.ContinueOuter(level as integer) Loop.ExitRoot() and Loop.ContinueRoot() where level 0 is the current loop, level 1 is the parent loop, level 2 is the parent or the parent loop…. etc Loop.HasExitedNormally returns false if any loop exited with an exit command (or Loop.Exitxxx method)

pricerc commented 5 years ago

BASIC will be 55 years old in the next week

  1. Which is why I'm sure that if this was a common requirement, it would have been raised before!

  2. This is VB.NET, not BASIC, but nonetheless, in the nearly 20 years the VB.NET's been a thing, this is not been seen as a significant shortcoming by the millions of users of this or most other programming languages. And I think the reasons for Python's rejection of the concept are part of the reason for this.

As far as I can tell, it is a very rare scenario. Certainly in over 30 years of programming, I've had at most a handful of occasions where something like this might have made my life a little easier. The fact is that 99% of the time break and continue are sufficient. The other 1% have trivial (GoTo) or structural (Refactoring) workarounds.

Can you provide a larger example of the problem you're trying to solve with this? It might help the rest of us understand it a bit better.

Oh, and I'm not opposed to this concept of a 'named loop', by the way (if it seems like I am), I just think it needs a bit more justification, because while the 'for loop' stuff is relatively trivial (because it already has a 'name'), the while loop is a more complicated piece of work (because we need to figure out how to name the loop), so needs a better justification.

VBAndCs commented 5 years ago

I certainly faced this many times, but I don't like goto so I use a Boolean flag like ExitOuterLoop like this:

Dim ExitOuterLoop = false
Do
   Do
          If Cond Then 
              ExitOuterLoop = true
              Exit Do
          End If
           ' …………
   loop
   If ExitOuterLoop Then Exit Do 
   ' …………
Loop

Which is longer and less efficient, but more structural and readable. By the way, I use Do/while loops as infinite loops most of the time and exit them from the body, because If I can determine a static end-loop condition, I can write a for loop. So, edit nested do loops is a common task for me.

pricerc commented 5 years ago

@VBAndCs that's just a normal structured programming technique that I learnt in my first year of university in the mid 80's using TurboPascal. And back then there was no 'break' and 'continue', so you'd have 'If' blocks all over the place. Even now, I avoid the use of 'Exit Do|Loop|Etc', because they're only just barely better than a GoTo, and some (e.g. my lecturers) would argue that they do not belong in a 'structured' language.

What I'm curious to see is a real-world example, not just mock-up, because I'm unable to think up an example that doesn't have a simple structured alternative. In my experience, there are usually several ways to do something, and I frequently struggle with one way of doing something before eventually finding out that there is a simple technique that I just didn't think of originally. It could be refactoring into multiple methods, or changing to a different loop construct. Or something funky with a Select Case.

I think this is is part of why this normal scenario hasn't come up in this forum - you're not the first person to want to exit a multi-layer loop, so that means that most people are either happy doing it the way you just described, or they've found an acceptable alternative.

Even if your proposal was accepted and implemented, it would be at best months away. In the mean time, while such questions would probably be better served by StackOverflow, the people on this forum may be able to suggest an alternative for the specific problems you're dealing with today. However, for that to happen, we'd all need to see a more complete example. Ideally, a link to a repo with some code that illustrates it.

VBAndCs commented 5 years ago

I can't search git hub for such usage. The two nested do loop is not such rare situation. The real problem ocuurs when you deal with three or more nested loops, which is rare except in math permutations, scintifice calcuations and 3d graphics. Maybe VB and yet C# ate rarely used in these areas because there are more efficient languages and tools for these apps, and .net intrinsic numeric types are not ment for these apps.

By the way: I want to comment on the BASIC thing: Visual Basic was a languge with visual Interface. Now, We have Only Basic.NET, but the Visual part doesn't belong to the language it self! In other words: Nothing Visual in VB.NET, so actualy it is just B.NET! And I wish we can rename the language to be: Microsoft Basic.Core (MB.Core) :).. Unless of course the language allows a visual code designer like the MVPL. Once again: My proposals are not issues, but aim to enhance the language design, and I am OK if you see some of them not suitable. Thanks.

pricerc commented 5 years ago

I can't search git hub for such usage. The two nested do loop is not such rare situation.

You have your own repository, you could put a sample up there and share it.

And if it's 'not rare', then you should have no trouble finding an example in your own code.

If you use the Windows or XAML Forms designers; they're still Visual, but you're right, they're strictly part of the wider framework, which was all about bringing the power of the VB 'visual design' experience to C#. And lately, it seems use of visual screen designers has been 'deprecated', which I think is unfortunate.

Today, Visual Basic is really just a name with some history, Changing it now would probably cause more confusion than just leaving it as-is. There are literally hundreds of BASIC dialects, I don't think it would be helpful to add more, so if the name was to change, I think it would have to be to something completely different, A bit like Pascal becoming Delphi.

Maybe A#?

VBAndCs commented 5 years ago

It's difficult to search my apps now. And I can libe with VB but I like .Core more :) I can offer a new workaround for the nested Do loops:

Do
   ' .....................
   If Not (Function()
       Do
            If Cond Then Return False
            ' …………….
       Loop
      )( ) Then Exit Do

Loop

It is strange that language allows such complex structure, but doesn't offer a simple exit outer solution :)

VBAndCs commented 5 years ago

By the way, this will be slightly better with my proposed Get blocks:

Do
   ' …………
   If Not (Get
       Do
            If Cond Then Return False
            ' …………….
       Loop
      End Get)  then Exit Do

Loop
VBAndCs commented 5 years ago

On second though, here is a funny workaround for the nested do loops:

While True
   ' …………
       Do
            If Cond Then Exit While
            ' …………….
       Loop
End While

Of course this can't always work with 3 nested loops, but it is rare! I will close this, but it inspired me with a new idea https://github.com/dotnet/vblang/issues/398#issuecomment-487169344. Thanks.

pricerc commented 5 years ago

@VBAndCs as I said; there are usually several alternatives. There are a few on StackOverflow as well (e.g. using Try/Catch blocks).

Your observation on the complexity of using the anonymous function one is interesting, because at face value, it seems more complicated than what you're asking for. But what's happened is that you've refactored your loop to use a method (one of the options I said was available to you). And the compiler's always been able to handle that.

It also happens to be a more 'structured' approach, because you no longer have 'jump' instructions (GoTo, Exit Loop).

pricerc commented 5 years ago

It's difficult to search my apps now.

I like to open a CMD prompt and use FindStr /s :)