Loop N Times

Forgive me if there's a better way.

Old way:

perhaps
for _ in 0..<N {}

or it could be

var i = 0
while i < N {
  i += 1
}

New way:

for N {}

I'm open to other syntax. The ask here is a concise way to loop N times.

1 Like

Also

for _ in repeatElement((), count: n) {
}
1 Like

There have been extensive discussions on this topic which you may be interested to read.

If, after reviewing these links, you feel like there's a new direction or perspective to be had on the topic, then by all means please do share! In that case, it can be helpful for the community if you'd write a short synopsis of what you learn from these readings so that we're not starting back at square one.

1 Like

IMHO not everything needs special language syntax, and this one looks quite well with just methods rather than extending the for loop.

Normally when doing such thing I resort to the below snippet which reads quite well:

(1..<10).forEach { print($0) }

if you really want an one liner sounds simple enough to write an extension for 10.times { _ in ... } onto integers :slight_smile:

3 Likes

You could use trailing closures (although, break becomes return, and you can't return from the outer function directly)

func loopN(_ count: Int, _ action: () -> ()) {
    for _ in 0 ..< count {
        action()
    }
}

loopN(10) {
    print("Hi!")
}

Don’t forget to rethrow. :wink:

Note that this code performs 9 repeats, not ten! :upside_down_face:

Sure. I don’t think we specifically are sticking to all examples having to use the same number, just an api shape hint (and typed on a phone :stuck_out_tongue:)

These replies leave me increasingly convinced it would be positive to add this language feature.

Addressing the suggestions:

func loopN(_ count: Int, _ action: () -> ()) {
    for _ in 0 ..< count {
        action()
    }
}

loopN(10) {
    print("Hi!")
}

This highlights that Swift could provide a built-in function rather than a new keyword/language feature. I prefer a new keyword/language feature.

If this solution were not built-in to Swift, it would have the issue of scalability (has to be written many times, perhaps once per app, or once per engineering group), and potential for incorrectness (range logic is easy to get wrong), and confusion. (Why are we creating a variable we don't need? Isn't that a bad practice? Is there a better way?)

(1..<10).forEach { print($0) }

The fact that people in this thread wonder whether you meant to do this 9 times or 10 times is great evidence of the unnecessary risk of this solution. Ranges are easy to get wrong. Extensions don't scale well for reasons listed above. Extensions also aren't available in all environments (e.g. online Swift learning, interviews, leetcode, etc). An extension on an integer is perhaps a little unintuitive. I don't consider repetition a property or functionality of an integer.

Conversely, a keyword and a count is simple to get right, enjoyable to write, and universally available.

for _ in repeatElement((), count: n) {}

If you dropped the repeat, it's similar to solution 1. Building a new function into Swift is an alternative to adding a new keyword/language feature. I don't have a terribly strong preference on which way to go.

Solution 1, if added to Swift, would be better than solution 3 for the proposed use-case. Creating a list of void whose elements are discarded adds levels of indirection. The engineer must think:

a) I'll create a list of N elements
b) What type should that be? Can I provide any sensible type?
c) I guess void works.
d) What about warnings from unused variables?
e) I guess I'll use the underscore
f) Is there a better way? Best practice is to avoid unused/discarded variables in the first place.
g) No, there isn't. (Perhaps we should add this language feature to Swift some day.)
h) Ok, let's just go with this.

To go back to my ask:

Every time I do:

for _ in 0..<N {} I cringe and consider whether Swift does have a better way. Based on the replies so far it seems Swift does not have a better way.

I haven't read the historical posts yet. I'll read that once I have a good time to do so. If anyone has the context and wishes to summarize why this hasn't happened yet, please do.

Can anyone provide a currently existing way that's actually better, than this below?

for N {}

Are there any disadvantages to adding it? The only I see are:

  1. Effort to add to Swift (minimal, I imagine).
  2. Additional concept for the engineer to keep in their head.

Regarding 2, I don't consider this a major issue as it's a very simple concept to understand. Any engineer who wishes not to learn it can simply use other ways. Anyone who comes across it for the first time can easily guess what it does.

The advantages of for N are clear. Simplicity is priceless in software engineering. Simplicity avoids the risk of bugs and makes it easier to solve hard problems. I also argue it's more enjoyable to write for N than for _ in 0..<N. If Swift doesn't have a concise/clean way to take a count and a scope and execute the scope count times, I still think it should be added.

If this is going to get added it would be better modeled on repeat. But I’m still not convince that the below is not sufficient.

repeat (n: 10) {...} 
3 Likes

If this is going to get added it would be better modeled on repeat . But I’m still not convince that the below is not sufficient.

repeat (n: 10) {...} 

This addresses the proposed change. I see a few main options for change:

  1. Add a keyword
    a. for
    b. repeat
  2. Add a function

I consider 1.a, 1.b, and 2 all positive changes if added to Swift. They're all easy to get right and easy to understand.

Regarding 1.a vs 1.b, I see the considerations being:

  • Downside 1.a is overloading for to have additional ways to use it. Upside to 1.b is its name is unambiguously tied to its exact usage.
  • Upside of 1.a is that for is already hard wired into many engineers as the go-to for repetition. It would be easy to remember.

At the end of the day I like for because I've been coding in C or the languages that took inspiration from C (Swift included) for nearly 20 years. for someone used to for, for is just easy to remember. I do consider repeat and for about equally good and would be happy with either.

Regarding the function, I like that as well. The keyword w/o the function is slightly more concise.

Is adding a keyword a "bigger" change?

One important thing a little beyond the syntax, would be the reason we want to repeat something n times. Why specifically n, a magic number, sufficiently large number, to allocate space for some collection? Because well, it won't matter how many times you repeat something for sufficiently large number and the problem with off-by-one error greatly diminished. Matching the collection is probably much quicker, and more concise with

for _ in matchingCollection {
}

rather than

for matchingCollection.count {
}

and that won't even work with (non-destructive) Sequence.

The case I can see for N {} as an improvement would be to run something for a magic number of times. I believe the need for this is much smaller than what the discussion made out to be.

The case I can see for N {} as an improvement would be to run something for a magic number of times. I believe the need for this is much smaller than what the discussion made out to be.

I cannot quantify how frequently this comes about. The examples you posted above are not exhaustive.

This ask is not coming from a "magic number" place. I've commonly come across this need while solving hard problems (i.e. harder than iterating a collection). The number is typically derived from math based on the input size or nature of the input.

Having an underscore in the code is a code/language smell to me. Not because it's unaesthetic, rather because it highlights that the language expects us to need a variable in the following scope, yet we do not.

The fact that we keep resorting to the underscore for solving this problem highlights the need for change imo.

I prefer the second code snippet because I don't judge solutions purely by conciseness. The lack of the unneeded variable underscore is benefit enough.

That's my point. This feature does not add any expressivity. It's dead-simple to implement. It doesn't add any composition to the existing features. The only strength would be the frequency this comes up, and I'm calling that into question.

That is the class of magic number I'm talking about. It's need not be a constant. That'd be the simplest ones.

Please read the above thread to see a discussion of the benefits of my proposal and the downsides of the other options. The existing options in Swift are less simple than what I'm proposing. What I'm proposing does remove a potential class of bugs.

Regarding quantification, have all past Swift evolution proposals been based on quantifiable data? Do we have datasets showing statistically significant Swift engineer usage of the language? If so, I'd be happy to quantify this for you. If not, perhaps we can still make a decision in the absence of quantity.

Numbers in code are not universally evil, nor are they all magic numbers.

See wikipedia's explanation of a magic number: "Unique values with unexplained meaning or multiple occurrences which could (preferably) be replaced with named constants"

I'm not referring to unique values with unexplained meaning or multiple occurrences.

Here's a concrete example where the problem can come about. I just happened to have another window open with some unfinished code:

var j = i
while j < chars.count {
    if isNum(chars[j]) {
        let compressionCount = readNum(chars, &j)
        j += 1 // move it off the open bracket
        for _ in 0..<compressionCount {
            
        }
    }
    j += 1
}

As you can see, compressionCount is not a magic number. It's the number of times a substring repeated prior to being compressed. Repetition in this case is not on a sequence, rather it has to happen that number of times.

In the above code, for count would be better than for _ in 0..<count because:

  1. Avoids range creation bugs
  2. Easier to read
  3. Subtly, the range in this context has no meaning. That's why we throw away the variable in each iteration.
  4. Avoids the code smell of unneeded variables/unneeded variables being discarded.
  5. More enjoyable to write

I'm not saying that quantification is needed, but other features does have something to sell along of those points I mentioned. For something that is literally a syntactic sugar (of for _ in 0..<n), the bar is usually even higher. The core team ofc could think differently from me. This is just my observation based on other proposals. Some sugars did get through, like implicit return in SE-0255.

Yes, it does. I'm not arguing with that. It removes any potential off-by-one if you are repeating things, exactly n times. It doesn't go much beyond that though. I can't use this with things like firstOffset and lastOffset, not neatly.

for lastOffset - firstOffset + 1 { .. }
for _ in firstOffset...lastOffset { .. }

Your syntax fixes a very specific class of off-by-one error, while leaving other classes intact. It's even more dangerous considering that off-by-one error can still appear in your syntax which is boasting to be free of (other) similar bugs.

Regardless, the unneeded variable is always there. You always need loop counter whether you use it or not. That you use _ to signify that you don't use it seems appropriate.

PS

Hmm, actually, it's more of a sufficiently large number case. Since many numerical algorithms I have in mind are something akin to keeps repeating, the stopping condition will be met within n cycle, and somehow got mistranslated to keeps repeating for n cycle. Anyhow, there's no point in going specific, we're really starting to play your-anecdote-my-anecdote here. My bad...

There is really no need to rehash the same conversation over again. Is there something that hasn’t already been discussed in the preceding threads I linked to above?

2 Likes

It sounds like you might have useful context here. Can you summarize for us?

One contrasting point of view from this thread in comparison to past threads is that repeat N {} or for N {} is not just for beginners. Other threads characterize repeat N {} as a language feature primarily or only useful for teaching or beginners. I actually come across a need for this feature most commonly while solving moderately difficult to very difficult algorithms.

Here's an interesting exercise: go solve 100 problems on leetcode. You'll quickly notice places where Swift falls flat:

  • iterating for a given count
  • ascii, ascii literals
  • data structures: circular buffer, heap/priority queue, maybe linked list.

There seems to be almost a macho trend amongst the prior threads, that we're all experts and therefore won't mess up ranges. As someone who interviews candidates frequently, I can tell you off by one errors are outstandingly common, even among programmers who are among the best in the world.

Using ranges to express something that isn't a range makes no sense to me. I haven't noticed anyone bring up this topic either.

Old: Given a count, create a range (hopefully correctly), ignore a counter, iterate.
New: Given a count, iterate

Chris Lattner mentioned the only reason Swift doesn't already have repeat N {} is due to resourcing. I don't see anything in those threads that seems definitive that Swift does not need this. What's stopping us from adding this?

1 Like

That's not quite how this process works. The question, rather, is whether there's anything which shows definitively that Swift needs this. Syntactic sugar has been among the lowest of all priorities in Swift Evolution, to the point of being actively discouraged, so the bar for justifying such a change is extremely high.

If you have a catalog of algorithms where you've needed this feature, feel free to share those here. Likewise, if you can show that Swift code in the wild has been buggy because of looping an incorrect number of times, that would be useful information. But repeating what has already been said another time would not clear that bar.

2 Likes

For anyone digging:

2 Likes
Terms of Service

Privacy Policy

Cookie Policy