Make Array.init(repeating: ...) with autoclosure

Array has a convenience init

public init(repeating repeatedValue: Element, count: Int)

It's useful when you need an array of equal elements, but it could be used for generating an array of different values, for example:

let randomInts = [Int](repeating: .random(in: 0...100), count: 100)

It can be possible with autoclosures:

public init(repeating repeatedValue: @autoclosure () -> Element, count: Int)

Or we can create new init

public init(generating generatedValue: @autoclosure () -> Element, count: Int)
1 Like

I confess that I have sometimes wanted this myself, but then I look at:

[Int](repeating: .random(in: range), count: 100)

vs.

(0..<100).map{ _ in Int.random(in: range) }

and recall that we already have a concise spelling for this operation, and that it leads naturally to other uses, like:

(0..<100).map { 2*$0 + 1 } // [1, 3, 5, ... ]

that the proposed operation doesn't permit.

1 Like

This example highlights a problem that it is not possible to know when you are reading the code what's going to happen.

foo(bar(), count: 100)

How many times is bar called? :thinking: Normally one. but what if parameter is marked with autoclosure? Or it is not marked so in the first version of "foo" API but then changed? Our code still compiles but possibly misbehaves because we are not prepared for "bar" to be called multiple times and we are not alerted of the API change.

Shouldn't we have some explicit marker at the use site to make it obvious that parameter is autoclosed?

foo(bar(), count: 100)             // 🛑 Error, autoclosure parameter usage must be explicit
foo(autoclosure bar(), count: 100) // ✅
foo(^ bar(), count: 100)           // ?

or something to that effect.

2 Likes

This is an old topic. (I've seen another big thread on it but can't find it.)

My issue with this approach is the meaningless 0 and the <. If you're going to disregard the argument, it could be anything. E.g.

stride(from: 0, to: 1000, by: 10)

I don't think polluting Array was or is the way to go. Array was just more of a go-to in the early days. map is fine; it just needs a better iterator to work off of.

func `repeat`(count: Int) -> some Sequence<Void> {
  repeatElement((), count: count)
}

`repeat`(count: 100).map { Int.random(in: 0...100) }
2 Likes

Using the exact same API for repeating a value n times for a function that generates a new value n times is a mistake. If you absolutely need to do this, and map isn't cutting it, then use this instead.

extension Array {
    public init(
        count: Int,
        generating value: () throws -> Element
    ) rethrows {
        guard count > 0 else {
            self.init()
            return
        }
        try self.init(unsafeUninitializedCapacity: count) {
            buffer, initializedCount in
            let base = buffer.baseAddress!
            while initializedCount < count {
                (base + initializedCount)
                    .initialize(to: try value())
                initializedCount += 1
            }
        }
    }
}
3 Likes