Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

for-else and while-else #1289

Open
StefanKarpinski opened this issue Sep 18, 2012 · 30 comments · May be fixed by #56153
Open

for-else and while-else #1289

StefanKarpinski opened this issue Sep 18, 2012 · 30 comments · May be fixed by #56153
Assignees
Labels
feature Indicates new feature / enhancement requests

Comments

@StefanKarpinski
Copy link
Member

Allow and else clause on for and while loops that executes if the loop never executes.

@quinnj
Copy link
Member

quinnj commented Jun 25, 2014

Bump. Can this be 0.4?

@mlhetland
Copy link
Contributor

I guess you've decided on this behavior—just thought I'd pipe in with an opinion. I find Python's behavior really useful here, where the else clause is executed if the loop has not been terminated early by break. The assumption is that you're looking for something, and the else clause is for if you don't find it. Maybe less seemingly logical than the suggested behavior, but I've had use for it many times, at least. In Python, it's also consistent with the else clauses when catching exceptions (try/catch/else), where the else clause is executed if there was no exception. The two seem quite analogous, and the else clause for try statements is also something I think would be really useful. I guess one could still have that even with the suggested behavior here, of course.

@StefanKarpinski
Copy link
Member Author

That Python for-else behavior has always struck me as very unintuitive, honestly. Mostly it's the name, not the functionality itself. The functionality can be emulated with gotos, but it is kind of awkward.

@elextr
Copy link

elextr commented Jul 12, 2014

Whilst there is nothing against this, at least in my experience the thing that is needed most often is to know if the loop was breaked (broken?) or if it completed.

But I have never found a "nice" way of specifying the required combination(s) of the three conditions, that the loop never ran, shortcut (break), or completed.

@mlhetland
Copy link
Contributor

Yeah, @StefanKarpinski, I guess it's mainly a matter of recycling keywords in Python. I think the naming is less awkward/surprising for try, though, if you see it as an alternative to the catch part. But, sure, the naming is more logical in the original feature spec. I can't recall having needed that functionality, but if it existed, I surely would ;-)

@ivarne
Copy link
Member

ivarne commented Jul 13, 2014

Two different suggestions for what one rarely used syntax should mean, seems like a bad sign.

@StefanKarpinski
Copy link
Member Author

Well, one would never guess at the Python meaning without already knowing it, whereas I think the meaning of executing if the main body never executes is pretty intuitive, but yes, that's still a point.

@prcastro
Copy link
Contributor

I know that it's a keyword not yet used on Julia, but I feel that the then keyword is more intuitive when doing what @mlhetland suggested. What do you think?

@mlhetland
Copy link
Contributor

Actually, @StefanKarpinski, I hadn't noticed there were gotos in Julia; saw them in the NEWS.mdfile for 0.3.0 now. Good enough for me—just a @goto instead of a break, and a @label instead of an else (though placed differently, of course). Better than, say, lots of Boolean flags and conditionals. Speaking of macros (and loops), would it be totally insane to adapt something like the Lisp LOOP macro for Julia? (Possibly in a separate library.) Not sure how useful it'd be. Just saw it mentioned in a (much-publicized) blog post on Julia vs Lisp, and implementing it (or something like it) sounded like fun ;-)

@wizzat
Copy link

wizzat commented Sep 24, 2014

This is a super useful feature. Has a resolution for this been decided or is everyone going to use goto?

@tonyhffong
Copy link

using @goto will trigger a lint warning, fyi.

@ivarne
Copy link
Member

ivarne commented Sep 25, 2014

@tonyhffong Why?

@tonyhffong
Copy link

er, I thought the whole point of for-loop and while-loop is to minimize the use of goto. Julia allows goto, that's fine, lint.jl doesn't have have a big-boy assumption.

@ivarne
Copy link
Member

ivarne commented Sep 25, 2014

TL;DR; I think an unconditional warning about goto/label belongs in a style checker, not in a linter.

I thought the point of the higher level constructs was to be able to express most code in a clearer more readable way. We had a thorough discussion before adding goto and found it to be better for some usages. Wikipedia defines lint as "tools that flag suspicious usage in software", and I don't think @goto and @label quailifies as suspicious unconditionally. If you could warn just for cases where it would be trivial to rewrite to a while-loop (or other constructs), it would be great and within the scope of a linter!

@wizzat
Copy link

wizzat commented Sep 25, 2014

I would say that people are much more likely to do something like this:

found = false
for x in 1:5
    if x == 3
        found=true
    end
end
if !found
    //error handling
end

than this:

for x in 1:5
    if x == 3
        @goto found
    end
end
//error handling
@label found

for/else and while/else are the natural and obvious solution, even if you rename the else clause. I saw a talk from a core python dev who wished they'd named "else" to be "nobreak". If we're so concerned about naming, perhaps nobreak is a good name. At least it's descriptive. (But for/else actually has a history in CS so it shouldn't really be so surprising to people...)

@tonyhffong
Copy link

@ivarne it's a grey area no? We judge "poor style" being one that can be prone to logical error. So where is the line between "prone to error" (lint ok) and "suspicious usage" (lint not ok)? Of course goto is not suspicious unconditionally, so are many current lint warnings, such as declaring an unused variable, dead branch, empty literal range [1:0]. This criterion seems inadequate. There's always judgment involved. Well currently it's mine, but it could be changed.

How about this, I could add lintpragma to silence the goto lint messages (btw, the current message is only at the INFO level, not at the higher WARN/ ERROR/ FATAL levels). Do you consider this meeting half-way?

@ivarne
Copy link
Member

ivarne commented Sep 26, 2014

The discussion about lint and @goto might continue in tonyhffong/Lint.jl#33

Please continue the for - else discussion here.

@cossio
Copy link
Contributor

cossio commented Sep 12, 2016

I prefer Python's behavior too. It's the one I use most often.

@2Cubed
Copy link
Contributor

2Cubed commented Jan 11, 2017

I think the else name makes sense, when thought about in the proper context. "loop, continue if 'successfully exited' (break), else run [block]."

This feature is quite helpful in Python, and I would love to see it in Julia, as well. 🙂

@StefanKarpinski
Copy link
Member Author

But for/else actually has a history in CS so it shouldn't really be so surprising to people...

Is there actually precedent for this construct in any language besides Python?

@Liso77
Copy link

Liso77 commented Sep 19, 2017

IMHO for-then-otherwise could be better than for-else because it is more complete and won't create confusion for pythonistas.

I propose also thinking about other use of for. For example:

[i for i in A then in B otherwise in C]

But I understand that then there could be questions/complications:

# what if cond(i) is false for every i in A? Does it invoke otherwise clause? 
 [i for i in A if cond(i) then in B otherwise in C]  
# inner loop only after then/otherwise?
[z for i in A then in B otherwise in C for z in [7*i] ]  

@StefanKarpinski StefanKarpinski modified the milestones: 2.0, 1.x Jan 22, 2019
@stords
Copy link

stords commented May 21, 2019

I know I'm reviving a super old thread, but I have been really missing this kind of syntax recently. What do people think about using the word "finally" ? Code in the finally clause executes if the loop ends successfully, and not if the loop is broken out of.

Its not 100% in line with the use in a try-finally context, but I think the word still makes sense, and would be appreciated by people used to the while-else syntax.

@cossio
Copy link
Contributor

cossio commented May 21, 2019

To be honest I no longer miss the for -- else thing (with the Python's meaning of executing the else if the loop is broken). I think I missed it before because I was too used to using Python, but now Julia has detoxified me ;)

See also #22891 for a more complete proposal.

However, in response to that thread, I would say that a function is perfectly fine for this (I post here to not revive that old thread):

function search_prime(values)
   for x in values
      isprime(x) && return x
   end
   return maximum(values) # makes no sense, but whatever
end

So, is there any good reason to add this feature? Like any real use case where it would really simplify code?

@StefanKarpinski
Copy link
Member Author

I written a fair amount of code recently that could really use the for else feature.

@quinnj
Copy link
Member

quinnj commented May 22, 2019

I've found myself wanting this recently as well.

@jeff-e
Copy link

jeff-e commented May 24, 2019

If you'll indulge me for a moment recapping this feature from my point of view, I can possibly add a note about scope.

All loop exits but the normal one have "instructions on exit" expressiveness, due to the embedded nature of breaks, so the new feature just evens the score:

for i in I # or while statement
  # …
  if exit_condition
    # …break's exiting instructions here…
    break
  end
  # …
  if exit_condition2
    # …2nd break's exiting instructions here…
    break
  end
  # …
then
  # …normal exiting instructions here…
end

...where I've used stand-in keyword then for the discussion and where break breaks out of the entire "loop-then" as expected.

Which is a quite general bit of control flow to leave out of a language.

Besides the obvious expressivity gain that Python gains from too, avoiding the need for boolean flags / other workarounds as usual for an omitted control flow structure, in Julia there is one further privilege of exiting instructions that is not expressible beyond the loop: loop scope.

Thus even in a loop absent any breaks one might choose to use then before giving up scope.

Am I mistaken?

This makes keyword else, already acknowledged as somewhat awkward, if acceptable, (and in some usage patterns readable), particularly awkward when used without break.

@stords:

Unfortunately, finally seems to be in spirit somewhat opposite what is needed, in its role providing exiting instructions regardless of how a block exited.

As an aside, if one really wanted to, finally could be given to loops to mean just that, "no matter how exited" (rather than the current error), appearing after then when both occur, and would merely allow code deduplication when all exits share common (scoped) code. But that is orthogonal to the question here, and I don't promote it specifically.

As to the painful situation of not obtaining the then feature until 2.0, I would say:

While finishing 1.0 the devs chose to kick the can on this til 2.0. Now we live with the wait. (Or in the meantime, are tempted to expropriate else whether compromisingly or not in the long term.) I suggest seeing it as another goodie awaiting us in 2.0 of which there must be many, the more of which will usher it in sooner.

Quick question @StefanKarpinski, I believe your most recent post refers to Python's meaning of else (and not to your OP meaning). If this github issue is now dedicated to that feature and not to the OP, including for milestoning, would you find it advisable to change the title to match? (Which is not characterized by else per se, though it may end up solved by else.) Just a question. Of course if you still want to consider the functionality of your OP, then this sidetrack we find ourselves in should perhaps all branch to a new issue.

@StefanKarpinski
Copy link
Member Author

StefanKarpinski commented May 24, 2019

Thus even in a loop absent any breaks one might choose to use then before giving up scope.

I'm not following what you're saying here about scope. Can you give an example to illustrate?

If this github issue is now dedicated to that feature and not to the OP, including for milestoning, would you find it advisable to change the title to match?

Yes, we're only considering the Python-like meaning at this point. The title seems to still be accurate, however, since the feature is applicable to both for and while loops as far as I can tell.

As to the painful situation of not obtaining the then feature until 2.0, I would say:
While finishing 1.0 the devs chose to kick the can on this til 2.0. Now we live with the wait.

for-else is currently a syntax error, so there's no need to wait until 2.0 for this feature. I find the statement of how hard it is to live without this feature a bit overstated. Many languages, including C, C++, Java and JavaScript have gotten by without this for their entire existence and even become respectably popular.

@jeff-e
Copy link

jeff-e commented May 27, 2019

I'm not following what you're saying here about scope. Can you give an example to illustrate?

Thanks, I guess I should've given an example.

Consider finding and storing the largest power of 2 less than a million. One might write

n = 2^0
while n < 1000000
  local old_n = n
  n = 2n
then
  n = old_n
end

(We could divide the final n by 2 instead of what's done here, but in an analogous example that might not be possible.)

Keyword else in this situation might be a bit bizarre.

I don't mean to suggest there aren't alternatives to the coding approach here; but rather, I mean this is a reasonable usage of the features of Julia once the new feature is adopted. So we can expect its use.

A keyword along the lines of then, not mentioned first by me, handles all use cases naturally.

The title seems to still be accurate, however, since the feature is applicable to both for and while loops as far as I can tell.

I was actually not referring to your use of for and while in the title, but to your use of else in the title (which presupposes the feature's syntax).

But no worries about the title, it's a title.

I find the statement of how hard it is to live without this feature a bit overstated. Many languages, including C, C++, Java and JavaScript have gotten by without this for their entire existence and even become respectably popular.

Acknowledged. Apologies for overstating this.

@KevOrr
Copy link

KevOrr commented Aug 30, 2019

Just wanted to point out that @StefanKarpinski's proposal is not inconsistent with the Python sense of for-else and while-else. For example (note how range(3) is changed to range(0) in the third loop):

In [1]: for i in range(3): 
   ...:     print(f'in loop. i = {i}') 
   ...: else: 
   ...:     print('in else') 
   ...:                                                                                                  
in loop. i = 0
in loop. i = 1
in loop. i = 2
in else

In [2]: for i in range(3): 
   ...:     print(f'in loop. i = {i}') 
   ...:     break 
   ...: else: 
   ...:     print('in else') 
   ...:                                                                                                  
in loop. i = 0

In [3]: for i in range(0): 
   ...:     print(f'in loop. i = {i}') 
   ...:     break 
   ...: else: 
   ...:     print('in else') 
   ...:                                                                                                  
in else

By adopting the python sense of for-else and while-else (or whatever else should be called), both proposals in this thread are satisfied

@mlhetland
Copy link
Contributor

That depends on how you read the proposal. At least to me,

Allow and else clause on for and while loops that executes if the loop never executes.

seems to be using a definitional if (see, e.g., p. 123 here), i.e., to define an else clause that is defined to execute exactly when (i.e., «if and only if») the loop never executes. That seems to be the point of it, really; «either execute the loop, or do this other thing» (though I may, of course, be misreading his intention…). And that's incompatible with Python's version (e.g., your first example).

I still regularly need the Python version (and have never so far needed the one suggested here) … but that's life :-}

@DilumAluthge DilumAluthge removed this from the 1.x milestone Mar 13, 2022
simeonschaub added a commit that referenced this issue Oct 14, 2024
This adopts the semantics discussed in #1289, namely the `else` block is executed whenever the loop never runs. Unlike Python, `break` and `continue` are irrelevant. Multidimensional loops are not supported since there is some ambiguity whether e.g.

```julia
for i in 1:3, j in 1:0
    print(1)
else
    print(2)
end
```

should print 2 once, thrice or maybe not even at all.

Currently only supported in the flisp parser, so requires `JULIA_USE_FLISP_PARSER=1`. I could use some guidance on the necessary steps to add this to JuliaSyntax as well - AFAIU this would also require #56110 first.

closes #1289
@simeonschaub simeonschaub linked a pull request Oct 14, 2024 that will close this issue
@nsajko nsajko added the feature Indicates new feature / enhancement requests label Oct 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Indicates new feature / enhancement requests
Projects
None yet
Development

Successfully merging a pull request may close this issue.