Apparently python has an optional "else" clause following for loops; but it's somewhat of a misnomer
see: http://shahriar.svbtle.com/pythons-else-clause-in-loops
it's really a kind of 'finally' for loops.
What I think would be quite cool would be an 'else' following a for loop for nim where the else is only run if the body of the loop does not run; e.g. the iterator was empty.
How often have you needed to do something like this:
for i in list:
echo "item: ", i
if list.len == 0: echo "No items"
Instead you could do this
for i in list:
echo "item: ", i
else:
echo "No items"
The macro system allows for:
ifEmpty:
for i in list: ...
do:
echo "No items"
Maybe too ugly?
Personally, I would write the example as:
if len(list) == 0:
echo "No items"
else:
for i in list:
echo "item: ", i
While this is a bit more typing, I find it easier to read compared to having two control structures merged into one. I have never used Python's else clause with loops, either, despite having known about it for two decades.
I think the normal syntax (Orion's "How often have you needed to do something like this:") is actually the cleanest.
for i in list:
echo "item: ", i
if list.len == 0: echo "No items"
I really don't understand Araq's example. ifEmpty implies indented code is what gets executed if empty, but the loop is there instead. The do really don't make sense there..
template forElse(list: expr, a, b: stmt): stmt {.immediate.} =
when list.len > 0:
for i in list:
a(i)
else:
b
forElse([5,6,7]) do (i: int):
echo "item: ", i
do:
echo "No items"
thow not very less typing, and less readable...
Orion: this really ought to be built into the language IMO
I respectfully disagree. I don't see the benefits, and I think it complicates the language unnecessarily. Even more so since the construct is not all that intuitive (a typical point of confusion with the Python construct is that some programmers expect the else clause to be terminated when the loop terminates prematurely via a break, not when it doesn't run at all [1]). The worst case scenario here is that a construct where (1) programmers are easily mistaken about the semantics and (2) applies to corner cases (empty lists and such) may introduce errors that are difficult to catch by testing.
I think that it could be much more intuitive than python's "else" (which is completely unintuitive).
If you 'return' out of an "if" statement, it doesn't run the corresponding "else" , why would you expect it to run the "else" if you break or return from a loop?
the "else" would only run if the body of the for loop is not run.
It's also safer and/or more efficient than the alternative syntax; i.e. it's safer to have this construct than to do an additional if to check if the loop ran, potentially re-executing the iterator.
Araq: Unless somebody comes up with an algorithm to transform 2 inline iterators into 1 parallel inline iterator. It's much more difficult than it looks.
It is possible (Sather did it, as far as I recall), but it requires non-trivial rewiring of control flow. You essentially need to transform the iterator into single-entry, single-exit form, such that the yield statement always occurs at the end. At this point, you can treat it pretty much like an inline procedure.
For example, consider this iterator:
iterator foo: int =
for i in 1..100:
if i mod 2 == 0:
yield i div 2
else:
yield (i-1) div 2
Assuming we had a goto statement, we could transform that to:
iterator foo: int =
var resume_point = 0
var result: int
iterate:
case resume_point
of 1: goto resume1
of 2: goto resume2
else: discard
for i in 1..100:
if i mod 2 == 0:
result = i div 2
resume_point = 1
goto back_to_caller
resume1:
else:
result = (i-1) div 2
resume_point = 2
goto back_to_caller
resume2:
return
back_to_caller:
yield result
goto iterate
If there's only one yield statement, of course you don't need a resume_point variable, since you'll always resume at the same place (the initialization part can then be hoisted out of the loop).
Well how do you think closure iterators work? That's exactly how they are rewritten internally. :-)
But good point, we should automatically derive a closure iterator from an inline iterator and not burden the poor programmer with implementation details.
template search(body: stmt): stmt {.immediate.} =
block:
var found {.inject.} = false
template failure: stmt =
break
template success: stmt =
found = true
break
body
template searchFor(T: typedesc, body: stmt): stmt {.immediate.} =
block:
var found {.inject.} = false
var what {.inject.}: T
template failure: stmt =
break
template success(x: T): stmt =
found = true
what = x
break
body
proc isPrime(x: int): bool =
if x in { 2, 3, 5, 7 }: return false
if x mod 2 == 0: return false
var i = 3
while i * i <= x:
if x mod i == 0:
return false
i += 2
return true
searchFor(int):
for i in 3000000..3000030:
if isPrime(i): success(i)
if found:
echo what
You can apply the same approach to your case:
template iterateAtLeastOnce(body: stmt): stmt {.immediate.} =
block:
var empty {.inject.}: bool
template foreach(i: expr, e: expr, s: stmt): stmt {.immediate.} =
empty = true
for i {.inject.} in e:
empty = false
s
body
iterateAtLeastOnce:
foreach(x, [1,2,3]):
echo x
if empty:
echo "Empty."
iterateAtLeastOnce:
foreach(x, ""):
echo x
if empty:
echo "Empty."
If you write a macro instead of a template to transform the AST, you can even use the normal for iteration syntax instead of the foreach template. The advantage of this approach is that the iterateAtLeastOnce surrounding template/macro and the if empty instead of else provide the reader with a clear hint as to what's going on.
Araq: Well how do you think closure iterators work? That's exactly how they are rewritten internally.
I suspected as much, but the point is that you can inline this code (unlike a closure iterator, you don't have to maintain the extra stack frame) and if an iterator has a single yield statement, there should essentially be zero overhead over current inlining, because all you're adding is a couple of additional goto statements that any decent optimizer should be able to throw away. And there's still less overhead even with multiple yield statements compared to a closure iterator (again, no separate stack frame that needs to be maintained between invocations).
Araq: If you are in ship mode, then by all means: SHIP! At this point even genius ideas should be deferred--not into the dustbin, but into some (real) future release plan. Shipping is a feature--the all-important feature. Even good ideas need to wait until their day.
As to the else after a loop, the word "else" is confusing in Python but the functionality is beneficial. I think the typical idiom is when a loop contains a break when some condition is met (like finding an element in a data structure, or getting within epsilon of an approximation)--in a sense, break is normal--and you want to do something if the loop cycles completely and doesn't break, which maybe is not what you wanted. Finally or "ifAll" seem closer to the intent. I recently found it handy for numerical approximation loops in Python.
Clearly, it can be deferred. An "if" after the loop can do the job. Perhaps the test condition is non-obvious or not clearly related to why the loop fully cycled. In such cases, the "finally" clause requires nothing special because the condition is implicit. Still--defer to another day.
So, it's useful. It can be renamed to be more obvious than Python.
AND there must be more important things to do: 1. documentation; 2. more documentation; 3. examples 4. useful additions to the standard library; 5. useful additions to the community/extended library; 6. enumerable cleanups that inevitably happen; 7. IDE support for popular, extensible IDEs; 8. Community building; 9. Gaining commercial adoption (a la Go, Rust, D). And not necessarily in that order...