Straightforward Code Repetition

In Swift, we often need to make our code repeat. Swift has made this easy to do while iterating through a range with a for loop. But you don't always need to iterate through a range when repeating code, take the following example.

struct Card {

    var rank: Rank
    var suit: Suit

    enum Rank {
        case ace
        case king
        case queen
        case jack
        case number(Int)
    }

    enum Suit: Int {
        case hearts = 1, diamonds, clubs, spades
    }

    static let newDeck: [Card] = { () -> [Card] in
        var deck = [Card]()
        for suits in 1...4 {
            for ranks in 1...13 {
                var currentRank : Card.Rank {
                    switch ranks {
                        
                    case 1: return .ace
                    case let n where 2...10 ~= n: return .number(n)
                    case 11: return .jack
                    case 12: return .queen
                    case 13: return .king
                    default: return .number(-1) //will never happen
                        
                    }
                }
                deck.append(Card(rank: currentRank, suit: Card.Suit(rawValue: suits)!))
            }
        }
        return deck
    }()
}

Now lets get a fresh deck of cards right out of the box:

var freshDeckOfCards = Card.newDeck

But I want to play a good game of poker, so we need to shuffle our cards. Here is our card shuffling extension:

extension Array where Element == Card {
    mutating func shuffle() {
        var shuffledArray = [Card]()
        shuffledArray.reserveCapacity(count)
        var totalCount : Int = count
        var tempArray : [Card] = self
        for _ in 0..<count {
            shuffledArray.append(tempArray.remove(at: Int(arc4random_uniform(UInt32(totalCount)))))
            totalCount -= 1
        }
        self = shuffledArray
    }
}

Lastly, this is the top of the notch casino, so we need to shuffle our deck at least 11 times:

for _ in 1...11 {
    freshDeckOfCards.shuffle()
}
//Our deck has been thoroughly shuffled

or lets take an exponentiation function for example

func myPow(_ base: Int, _ exponent: Int) -> Int {
    var n = 1
    for _ in 1...exponent {
        n *= base
    }
    return n
}

var fifteenExponentTwo = myPow(15, 2) //225

In the last step of both of the provided examples lies the ambiguous part of our code that I would like to fix. The need to use an underscore and a range may be confusing to some new developers as all they need is to repeat a piece of code a specified number of times rather than iterate through a range. Also it is redundant code that makes overall readability suffer.

My Solution - repeat for

My solution involves the creation of a new, simple type of loop, a repeat for loop.
In this loop you don't iterate through anything (range, array, etc.), the code within the loop is repeated for the specified number of times. To show you what I mean, let's fix our exponent function, myPow.

func myPow(_ base: Int, _ exponent: Int) -> Int {
    var n = 1
    repeat for exponent { //repeat for loop in place of the for loop
        n *= base
    }
    return n
}

var fifteenExponentTwo = myPow(15, 2) //225

Note: this will display an error if a user inputs a number less than 1.

One of the main and clearest benefits of this code is its straightforwardness and ergo, improve overall readability and reduce seemingly unnecessary code.


Naming Conventions

Some of the most popular naming suggestions that have been suggested include:

  • do n times {...}
  • repeat for n {...}
  • repeat n {...}
  • do n {...}
  • repeat {...} for n

And more naming conventions and suggestions are welcomed.


1 Like

That's not really a solution for anything. We basically already have that.

for _ in 0..<exponent {
    // do whatever you like exponent-times
}

The 'problem' is not big enough to warrant extra keywords or syntax.

BTW A shuffled() function has already been added to collections in the stdlib.
;-)

1 Like

This has been discussed a few times, and I think I would summarise that discussion by saying it is disputed as to whether a solution is warranted here. I do think for _ in 0 ..< count has some undesirable aspects, particularly the potential for off-by-one errors in 0 ..< count, 0 ... count, 1 ..< count, 1 ... count, where the variable name and context might not make it clear if n or n±1 iterations are desired.

This is not an unheard of construct in programming languages, e.g. just poking around:

  • COBOL: PERFORM n TIMES
  • Common Lisp: dotimes
  • REBOL: loop n
  • Ruby: n.times

But I wouldn't say it is common either. The current Swift way of writing this is more common across programming languages. So some sort of repeat n times construct might be nice in Swift, particularly for teaching/learning purposes, but perhaps people are better off learning the more flexible form up front, even if it is more complex and error-prone.

That covers the general idea, as for the specific pitch I think repeat for 11 doesn't read particularly well. repeat 11 times or making it a .times method on integers like Ruby don't feel right for Swift to me either. Maybe a top level function like repeat(times: n) or repeat(count: n) would make sense, but then you hit all the issues around closures versus control flow (i.e. break, continue, etc). There has been some discussion about making those features available for closures so people can implement more natural custom control flow, but that's another rabbit hole.

1 Like

I'm generally against minor syntactic sugar like this, but I am actually +1 on this, perhaps for a surprising reason: Logo. The Logo language is widely used as a very early teaching language, and it has a similar concept. Swift is used in the iPad playgrounds app for education, and its first introduction to loops has to introduce the range operator like for x in 0 ..< 10 { as a magic "trust us and don't touch us" thing. This has always bothered me.

A long time ago (Swift 2 I think) we resyntaxed do/while into the current repeat/while loop. That was done for a number reasons (e.g. involving the introduction of do/catch), but one thing I hoped at the time is that this would make it "obvious" that we should use it to introduce a counted loop construct, spelled something like repeat 10 {.

There is very little reason to add this sort of thing for an iOS programmer, and very little to justifying adding complexity to the language, but it does smooth out one very small bump in progressive disclosure of complexity for a very important-to-me audience, so perhaps it is worth it.

-Chris

15 Likes

Okay so this is probably a bad idea. But I have to ask...

Would ad-hoc extensions to the syntax be possible in the future? Maybe not macros (although that was my first thought; I've seen /* confusing */ C macros that implement foreach syntax), but something with more um... knowledge of the language.

This would allow syntax-extension libraries to be made. Something like "hey, this lib has constructs that make this pattern very explicit", or "hey, use this lib for very easy and clear syntax to teach kids".

Does this make sense?

One of the important points about playgrounds is that it is teaching "real swift code", not a weird dialect or subset of Swift.

4 Likes

When I was writing my previous comment I was thinking that repeat 10 { … }/repeat length { … } could theoretically be confusing in some contexts (repeating 10 times versus repeating the number 10 in some sense), but on reflection it doesn't seem like a big deal or a likely interpretation, and is very learnable anyway.

Ya, as @ChrisLattner put it,

it does smooth out one very small bump in progressive disclosure of complexity for a very important-to-me audience – Chris Lattner

One of the major motivations for a feature like this would be for the ease of teaching as it provides a easier to understand syntax right off the bat, while simultaneously introducing them to actual Swift code and syntax.

I’m actually OK with something like this, but to play devil’s advocate, if the main or only purpose of repeat 10 is for teaching, then it isn’t actually “real code” you’re teaching in a meaningful sense.

As to syntax, there’s the issue of whether repeat 1 means something is performed once or twice. If the English language gets a say, it really ought to be twice, and by induction repeat 10 ought to execute the loop 11 times—but that’s bonkers. I wonder if we would consider a syntax such as do 10 times { ... }.

1 Like

What about using a syntax like:

execute for 10 {
    doSomething()
}

I guess break and continue aren't important for that audience, so

10.times { print("I shall not use this method with negative numbers") }

works well for them (and needs no change in the language).

1 Like

@Tino, I just wanted to point that the goal is not to dumb it down completely. It's primary use is to help teach loops with an easy to understand syntax, not to take away some of the primary features of loops like break and continue. Therefore, it should stay as a loop rather than an extension.

Then how about:

var x = 0
please: do {
    print(x)
    x = x + 1
    if x < 7 { continue please }
}

(Needs no change to the language or anything, and reads almost like prose. (And it will not compile unless you say please.))


If a new loop construct should be introduced, in order to reduce clutter and be clearly readable for beginners, I think this would be clearer:

do 10 times { ... }

than

repeat for 10 { ... }

and

repeat 10 { ... }
4 Likes

Very polite. However, in the English language (as opposed to Swift where the behaviour is nailed down), it would be a little ambiguous as "continue" could be read to mean stop looping and continue with the instructions after the loop.

If we really do want to add the feature to the language, I like

do 10 times { ... }

as it avoids the issue that Chris raised where repeat n strictly means do it n + 1 times. However, i would question the necessity for the keyword times, so it would be

do 10 { .... }

I don't think adding more syntax to a language makes it easier to learn... if that would be true, we should also have until to complement while, unless to remove the need for negation ("!" isn't that intuitive...), and maybe lots of other tiny additions.
Nowadays, it is rarely necessary to use anything but a for loop, so when you take the tiny share of use cases for plain repetition, and subtract the number of cases where you don't need to break out the loop, I bet we would end up with a extremely small number:
Imho it's somewhat odd to use a loop variant whose key characteristic is to repeat a fixed number of times - and then add exceptions inside the loop, which most likely are better calculated in advance.

1 Like

@Chris_Lattner3

I do. Compare the following snippet from real-world iOS UI tests:

for _ in 0..<5 {
    app.buttons["Add 30 Puzzles"].tap()
}

repeat 5 {
    app.buttons["Add 30 Puzzles"].tap()
}

Both _ and ..< are nothing but noise. Plus ... vs ..< is prone to off-by-one errors.

5 Likes

If we want to play around with do, repeat, and/or for; and not come up with combinations that would nearly be legitimate current code; how about:

repeat { /*...*/ } for N

where N has to be a value that conforms to BinaryInteger. There's no while to complete a repeat-while, so the "for" has to be part of the new repeat-for statement and not an independent statement.

It would be translated like:

var counter = N
while counter > 0 {
    defer { counter -= 1 }
    // original code block
}

Remember that N's type has to match BinaryInteger, so the test and iteration code should work unless the type doesn't support values of either 0 and/or 1.

Edit: Changed the comparison from "<" to ">".

2 Likes

Naive and late contribution... would something like the following not solve the OP's problem?

extension Int {
	func timesRepeat(_ block: ()->()) {
		for _ in 0..<self {
			block()
		}
	}
}

11.timesRepeat {
    deck.shuffle()
}

I'd just called it times and make it rethrows

2 Likes

Sure, but as mentioned previously in the thread this has some issues because it doesn't support the expected control flow features, e.g. break and continue. It would be nice if there was a general way for this functionality to be supported in closures somehow, like being translated into throwing a BreakError or similar.

1 Like