[Pitch] Improve `init(repeating:count)`

This is a small pitch which I will abandon if there is not much appetite for such improvement. ;)

I would like to propose an improvement to an initializer of all collection types that provide: init(repeating repeatedValue: Element, count: Int).

This change is meant to support reference type initialization on each iteration of the internal for-loop rather than copying the same references n times.

The change would be really straightforward and should not break existing code, except that the behavior would change for class types.

Instead of:

init(repeating repeatedValue: Element, count: Int)

let threeViews = Array(repeating: UIView(), count: 3) // contains 1 view 3 times
we would have:

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

This simple change would allow us to construct an array of different objects instead of an array with n references to the same object.

let threeViews = Array(repeating: UIView(), count: 3) // contains 3 different views

2 Likes

+1

···

On Aug 17, 2017, at 2:38 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

This is a small pitch which I will abandon if there is not much appetite for such improvement. ;)

I would like to propose an improvement to an initializer of all collection types that provide: init(repeating repeatedValue: Element, count: Int).

This change is meant to support reference type initialization on each iteration of the internal for-loop rather than copying the same references n times.

The change would be really straightforward and should not break existing code, except that the behavior would change for class types.

Instead of:

init(repeating repeatedValue: Element, count: Int)

let threeViews = Array(repeating: UIView(), count: 3) // contains 1 view 3 times
we would have:

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

This simple change would allow us to construct an array of different objects instead of an array with n references to the same object.

let threeViews = Array(repeating: UIView(), count: 3) // contains 3 different views

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

This would be very source-breaking, no?

···

On Thu, Aug 17, 2017 at 04:47 Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

+1

On Aug 17, 2017, at 2:38 AM, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

This is a small pitch which I will abandon if there is not much appetite
for such improvement. ;)

I would like to propose an improvement to an initializer of all collection
types that provide: init(repeating repeatedValue: Element, count: Int).

This change is meant to support reference type initialization on each
iteration of the internal for-loop rather than copying the same references
n times.

The change would be really straightforward and should not break existing
code, except that the behavior would change for class types.

Instead of:

init(repeating repeatedValue: Element, count: Int)

let threeViews = Array(repeating: UIView(), count: 3) // contains 1 view 3 times

we would have:

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

This simple change would allow us to construct an array of different
objects instead of an array with n references to the same object.

let threeViews = Array(repeating: UIView(), count: 3) // contains 3 different views

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

1 Like

I don't think that changing the existing initialiser is the way to do this, as it's not really clear here that the UIView() will be instantiated multiple times rather than the same reference copied multiple times. The copying behaviour is in fact desirable as it will be significantly faster, especially with very large arrays.

I think the better way to achieve this might be add some closure-based sequence that returns a closure's result N times, so we could do something like this:

  let threeViews = Array(Repeat(count: 3) { return UIView() })

i.e- we would initialise Array from a sequence returning a new UIView three times. While we could also do an array initialiser with closure, using a separate type is probably the cleaner way to do this.

There is already a Repeater type, but this just does the same as the array initialiser already does, could be another type I've missed that might do what is needed, otherwise it seems to require types like AnyIterator with some counting code.

···

On 17 Aug 2017, at 10:38, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

This is a small pitch which I will abandon if there is not much appetite for such improvement. ;)

I would like to propose an improvement to an initializer of all collection types that provide: init(repeating repeatedValue: Element, count: Int).

This change is meant to support reference type initialization on each iteration of the internal for-loop rather than copying the same references n times.

The change would be really straightforward and should not break existing code, except that the behavior would change for class types.

Instead of:

init(repeating repeatedValue: Element, count: Int)

let threeViews = Array(repeating: UIView(), count: 3) // contains 1 view 3 times
we would have:

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

This simple change would allow us to construct an array of different objects instead of an array with n references to the same object.

let threeViews = Array(repeating: UIView(), count: 3) // contains 3 different view

In terms of functionality yes potentially a breaking change, because the behavior would change for objects that were initialized through the passed expression.

Personally it always bugged me that it does not work for objects and forced map usage over a range to create an array of different objects.
However, this change feels like a fix to the intended behavior, but yes it might be very source breaking.

Yet again it’s up to the community to decide if we need this or not. ;)

Am 17. August 2017 um 12:59:06, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

This would be very source-breaking, no?

···

On Thu, Aug 17, 2017 at 04:47 Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:
+1

On Aug 17, 2017, at 2:38 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

This is a small pitch which I will abandon if there is not much appetite for such improvement. ;)

I would like to propose an improvement to an initializer of all collection types that provide: init(repeating repeatedValue: Element, count: Int).

This change is meant to support reference type initialization on each iteration of the internal for-loop rather than copying the same references n times.

The change would be really straightforward and should not break existing code, except that the behavior would change for class types.

Instead of:

init(repeating repeatedValue: Element, count: Int)

let threeViews = Array(repeating: UIView(), count: 3) // contains 1 view 3 times
we would have:

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

This simple change would allow us to construct an array of different objects instead of an array with n references to the same object.

let threeViews = Array(repeating: UIView(), count: 3) // contains 3 different views

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Alternatively, instead of replacing the current definition with an autoclosure version, we could leave the current version in place and add a version taking a function. This could be especially useful when you’ve defined a function like `makeMySpecialKindOfButton() -> UIButton` that does a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

···

On Aug 17, 2017, at 7:54 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 17 Aug 2017, at 10:38, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

This is a small pitch which I will abandon if there is not much appetite for such improvement. ;)

I would like to propose an improvement to an initializer of all collection types that provide: init(repeating repeatedValue: Element, count: Int).

This change is meant to support reference type initialization on each iteration of the internal for-loop rather than copying the same references n times.

The change would be really straightforward and should not break existing code, except that the behavior would change for class types.

Instead of:

init(repeating repeatedValue: Element, count: Int)

let threeViews = Array(repeating: UIView(), count: 3) // contains 1 view 3 times
we would have:

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

This simple change would allow us to construct an array of different objects instead of an array with n references to the same object.

let threeViews = Array(repeating: UIView(), count: 3) // contains 3 different view

I don't think that changing the existing initialiser is the way to do this, as it's not really clear here that the UIView() will be instantiated multiple times rather than the same reference copied multiple times. The copying behaviour is in fact desirable as it will be significantly faster, especially with very large arrays.

I think the better way to achieve this might be add some closure-based sequence that returns a closure's result N times, so we could do something like this:

  let threeViews = Array(Repeat(count: 3) { return UIView() })

i.e- we would initialise Array from a sequence returning a new UIView three times. While we could also do an array initialiser with closure, using a separate type is probably the cleaner way to do this.

There is already a Repeater type, but this just does the same as the array initialiser already does, could be another type I've missed that might do what is needed, otherwise it seems to require types like AnyIterator with some counting code.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The closure should go on the end though…

···

On Aug 17, 2017, at 8:40 AM, Christopher Kornher via swift-evolution <swift-evolution@swift.org> wrote:

We might as well add the index to the call so elements can be created from other lists, etc.

Array(repeatedlyCalling: { (index:Int) in MyView( forIndex:index ) }, count: 3) // This might by syntactically correct...

On Aug 17, 2017, at 9:24 AM, Rob Mayoff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Alternatively, instead of replacing the current definition with an autoclosure version, we could leave the current version in place and add a version taking a function. This could be especially useful when you’ve defined a function like `makeMySpecialKindOfButton() -> UIButton` that does a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

We might as well add the index to the call so elements can be created from other lists, etc.

Array(repeatedlyCalling: { (index:Int) in MyView( forIndex:index ) }, count: 3) // This might by syntactically correct...

···

On Aug 17, 2017, at 9:24 AM, Rob Mayoff via swift-evolution <swift-evolution@swift.org> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Alternatively, instead of replacing the current definition with an autoclosure version, we could leave the current version in place and add a version taking a function. This could be especially useful when you’ve defined a function like `makeMySpecialKindOfButton() -> UIButton` that does a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

This is still source-breaking, because an array of closures is legal. We
need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

···

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution < swift-evolution@swift.org> wrote:

Alternatively, instead of replacing the current definition with an
autoclosure version, we could leave the current version in place and add a
version taking a function. This could be especially useful when you’ve
defined a function like `makeMySpecialKindOfButton() -> UIButton` that does
a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

1 Like

Good catch. I think this functionality is worth adding. Seems like it could be useful for e.g., a grid of buttons.

···

On Aug 17, 2017, at 11:25 AM, Rob Mayoff via swift-evolution <swift-evolution@swift.org> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:
Alternatively, instead of replacing the current definition with an autoclosure version, we could leave the current version in place and add a version taking a function. This could be especially useful when you’ve defined a function like `makeMySpecialKindOfButton() -> UIButton` that does a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Couldn't this be rewritten more simply today as:

    Array((0..<3).map { index in MyView(forIndex: index) })

And the version that doesn't need the index could be written:

    Array((0..<3).map { _ in UIView() })

The AnyIterator approach posted above is also nice—I wouldn't have thought
of that one. But I suppose that only works in the case where you don't need
the index.

So the question is, should we increase the API surface of Array for
something that (IMO) is already fairly straightforward to do? The nice
thing about the approaches above is that they're composable. Array doesn't
have to make any assumptions about closures or index arguments to those
closures; it just takes a sequence and we already have primitives to
construct sequences of new objects using .map. Array(repeating:count:) and
repeatedElement(_:count:) are nice when the value being repeated is fixed,
but I think once you start getting into questions like "what if I need a
different thing each time?" or "what if I need to involve the index as part
of the elements?" then it's better suited to compose the features already
there to build something up than to add new APIs that try to cover each of
these special cases.

···

On Thu, Aug 17, 2017 at 8:40 AM Christopher Kornher via swift-evolution < swift-evolution@swift.org> wrote:

We might as well add the index to the call so elements can be created from
other lists, etc.

Array(repeatedlyCalling: { (index:Int) in MyView( forIndex:index ) },
count: 3) // This might by syntactically correct...

On Aug 17, 2017, at 9:24 AM, Rob Mayoff via swift-evolution < > swift-evolution@swift.org> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution < > swift-evolution@swift.org> wrote:

Alternatively, instead of replacing the current definition with an
autoclosure version, we could leave the current version in place and add a
version taking a function. This could be especially useful when you’ve
defined a function like `makeMySpecialKindOfButton() -> UIButton` that does
a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We
need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

1 Like

I don't think it's worthwhile adding new API.
I think this is simple enough:

let views = Array(AnyIterator(UIView.init).prefix(3))

···

2017-08-17 21:04 GMT+09:00 Robert Bennett via swift-evolution < swift-evolution@swift.org>:

Alternatively, instead of replacing the current definition with an
autoclosure version, we could leave the current version in place and add a
version taking a function. This could be especially useful when you’ve
defined a function like `makeMySpecialKindOfButton() -> UIButton` that does
a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

On Aug 17, 2017, at 7:54 AM, Haravikk via swift-evolution < > swift-evolution@swift.org> wrote:

On 17 Aug 2017, at 10:38, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

This is a small pitch which I will abandon if there is not much appetite
for such improvement. ;)

I would like to propose an improvement to an initializer of all collection
types that provide: init(repeating repeatedValue: Element, count: Int).

This change is meant to support reference type initialization on each
iteration of the internal for-loop rather than copying the same references
n times.

The change would be really straightforward and should not break existing
code, except that the behavior would change for class types.

Instead of:

init(repeating repeatedValue: Element, count: Int)

let threeViews = Array(repeating: UIView(), count: 3) // contains 1 view 3 times

we would have:

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

This simple change would allow us to construct an array of different
objects instead of an array with n references to the same object.

let threeViews = Array(repeating: UIView(), count: 3) // contains 3 different view

I don't think that changing the existing initialiser is the way to do
this, as it's not really clear here that the UIView() will be instantiated
multiple times rather than the same reference copied multiple times. The
copying behaviour is in fact desirable as it will be significantly faster,
especially with very large arrays.

I think the better way to achieve this might be add some closure-based
sequence that returns a closure's result N times, so we could do something
like this:

let threeViews = Array(Repeat(count: 3) { return UIView() })

i.e- we would initialise Array from a sequence returning a new UIView
three times. While we could also do an array initialiser with closure,
using a separate type is probably the cleaner way to do this.

There is already a Repeater type, but this just does the same as the array
initialiser already does, could be another type I've missed that might do
what is needed, otherwise it seems to require types like AnyIterator with
some counting code.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

(0..<3).map{ _ in UIView() } - map already returns an Array.

Array((0..<3).map{ _ in UIView() }) is redundant.

I've fallen foul before, of trying to create an array of six buttons and
getting an array of one button six times; I think that should be easier.
But if each button corresponds to an existing value or needs to be
initialised based on its index in that array, map transposing values or
indices into buttons is already covered.

···

On Thu, Aug 17, 2017 at 5:03 PM, Tony Allevato via swift-evolution < swift-evolution@swift.org> wrote:

Couldn't this be rewritten more simply today as:

    Array((0..<3).map { index in MyView(forIndex: index) })

And the version that doesn't need the index could be written:

    Array((0..<3).map { _ in UIView() })

The AnyIterator approach posted above is also nice—I wouldn't have thought
of that one. But I suppose that only works in the case where you don't need
the index.

So the question is, should we increase the API surface of Array for
something that (IMO) is already fairly straightforward to do? The nice
thing about the approaches above is that they're composable. Array doesn't
have to make any assumptions about closures or index arguments to those
closures; it just takes a sequence and we already have primitives to
construct sequences of new objects using .map. Array(repeating:count:) and
repeatedElement(_:count:) are nice when the value being repeated is fixed,
but I think once you start getting into questions like "what if I need a
different thing each time?" or "what if I need to involve the index as part
of the elements?" then it's better suited to compose the features already
there to build something up than to add new APIs that try to cover each of
these special cases.

On Thu, Aug 17, 2017 at 8:40 AM Christopher Kornher via swift-evolution < > swift-evolution@swift.org> wrote:

We might as well add the index to the call so elements can be created
from other lists, etc.

Array(repeatedlyCalling: { (index:Int) in MyView( forIndex:index ) },
count: 3) // This might by syntactically correct...

On Aug 17, 2017, at 9:24 AM, Rob Mayoff via swift-evolution < >> swift-evolution@swift.org> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution < >> swift-evolution@swift.org> wrote:

Alternatively, instead of replacing the current definition with an
autoclosure version, we could leave the current version in place and add a
version taking a function. This could be especially useful when you’ve
defined a function like `makeMySpecialKindOfButton() -> UIButton` that does
a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We
need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Also, for those of you here who haven't heard my previous rant on the subject, I dislike using map for generating values that don't depend on transforming a domain to a range. (It has been argued that `_ in` is mapping from `Void`, but I still dislike it immensely)

Can you please elaborate why (or maybe point me at the rant)? Does the same argument apply to `for _ in 1..<N { … }`?

But back to the discussion. I quickly looked at the sil being generated, and measured the performance of:

public class C {
  init() {}
}

@inline(never)
func foo(n: Int) -> [C] {
  var result: [C] =
  result.reserveCapacity(n)
  for _ in 1...n {
    result.append(C())
  }
  return result
}

@inline(never)
func bar(n: Int) -> [C] {
  return (1...n).map { _ in C() }
}

Version using map wins in both the size of sil and performance. It is also easier to implement right (fewer moving parts, I guess). If one forgets to call `reserveCapacity` on `result` in the first case, performance drops even further.

Max

···

On Aug 17, 2017, at 10:05 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Here are the ways that I have approached this:

// Ugh
[UIView(), UIView(), UIView(), UIView(), UIView()]

// No:
let viewsA = Array(repeating: UIView(), count: 5)
// You end up with 5 of the same view

// Two statements that really should be one
var views: [UIView] =
for _ in 1 ... 5 { views.append(UIView()) }

// Wrong use of `map`, because it's mapping over `Void`
let viewsA = (1 ... 5).map({ _ in UIView() })

// You can introduce `collect` specifically for the `_ in` in case, matching other languages:
public func collect<T>(_ generator: () throws -> T) rethrows -> [T]

// I think these are just ugly
let viewsA__ = sequence(first: UIView(), next: { _ in UIView() }).lazy.prefix(5)
let viewsB__ = sequence(state: 5, next: { defer { $0 -= 1 }; $0 == 0 ? nil : UIView() }

// You can build an iterator

let labeler = AnyIterator({ return UILabel() })
let labels4 = Array(labeler.prefix(5))

// which lets you create multiple "slices" off the iterator

let randoms = AnyIterator({ return Int(arc4random_uniform(100) )})

print(Array(randoms.prefix(5)))
print(Array(randoms.prefix(5)))

// A little complex and I don't like making this `Int` based, because it pulls the semantics away from sequences/collections

extension Int {
    func of<T>(_ generator: @autoclosure () -> T) -> [T] {
        assert(self >= 0, "cannot generate negative-count collection")
        return (0 ..< self).map({ _ in generator() })
    }
}

5.of(UIView())

// Even worse
protocol Constructable {
    init()
}

extension UIView: Constructable {}

extension Int {
    func of<T: Constructable>(_ generator: @autoclosure () -> T) -> UnfoldSequence<T, Int> {
        assert(self > 0, "cannot generate negative-count collection")
        return sequence(state: self, next: { (state: inout Int) -> T? in
            defer { state -= 1 }; return state == 0 ? nil : T.init() })
    }
}

print(Array(5.of(UIView())))

// or

extension Int {
    func of<T>(_ generator: @escaping @autoclosure () -> T) -> LazyMapRandomAccessCollection<(CountableRange<Int>), T> {
        assert(self > 0, "cannot generate negative-count collection")
        return (0 ..< self).lazy.map({ _ in generator() })
    }
}

// where some people preferred calling this `elements`, for example `5.elements(of: UIView())`

// You can go Array:

extension Array {
    /// Creates a new array containing the specified number of values created by repeating a generating closure.
    ///
    /// - Parameters:
    /// - count: The number of times to apply the closure. `count` must be zero or greater.
    /// - generator: The closure to execute
    public init(count: Int, repeating: () -> Element) {
        precondition(count >= 0, "")
        self.init((1 ... count).map({ _ in repeating() }))
    }
}

let labels = Array(count: 4) { UILabel() }

// Or similarly

extension Array {
    init(repeat count: Int, byCalling generator: @autoclosure () -> Element) {
        self = (1 ... count).map({ _ in generator() })
    }
}

// From Oliver H:

extension Array {
  convenience init(count: Int, repeating: () -> Elements) {
    self = Array( (0..<count).map { _ in repeating() } )
  }
}

let views: [UIView] = Array(count: 5) { UIView(frame: .zero) }

// This one is from Soroush K. I think it's clever but I wouldn't really use it

func * <T>(generator: @autoclosure () -> T, n: Int) -> [T] {
  (0..<n).map({ _ in generator() }
}

UIView() * 5

On Aug 17, 2017, at 10:36 AM, Tony Allevato via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Thu, Aug 17, 2017 at 9:20 AM Ross O'Brien <narrativium+swift@gmail.com <mailto:narrativium%2Bswift@gmail.com>> wrote:
(0..<3).map{ _ in UIView() } - map already returns an Array.

Array((0..<3).map{ _ in UIView() }) is redundant.

Ah, right, thanks for pointing that out. I couldn't remember off the top of my head whether it returned an array or some kind of special sequence type that would need to be converted over.

In that case, I still think the map version wins—it's very clear that a repeated *operation* is occurring, whereas the originally proposed @autoclosure version hides that very important semantic information.

I've fallen foul before, of trying to create an array of six buttons and getting an array of one button six times; I think that should be easier. But if each button corresponds to an existing value or needs to be initialised based on its index in that array, map transposing values or indices into buttons is already covered.

On Thu, Aug 17, 2017 at 5:03 PM, Tony Allevato via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Couldn't this be rewritten more simply today as:

    Array((0..<3).map { index in MyView(forIndex: index) })

And the version that doesn't need the index could be written:

    Array((0..<3).map { _ in UIView() })

The AnyIterator approach posted above is also nice—I wouldn't have thought of that one. But I suppose that only works in the case where you don't need the index.

So the question is, should we increase the API surface of Array for something that (IMO) is already fairly straightforward to do? The nice thing about the approaches above is that they're composable. Array doesn't have to make any assumptions about closures or index arguments to those closures; it just takes a sequence and we already have primitives to construct sequences of new objects using .map. Array(repeating:count:) and repeatedElement(_:count:) are nice when the value being repeated is fixed, but I think once you start getting into questions like "what if I need a different thing each time?" or "what if I need to involve the index as part of the elements?" then it's better suited to compose the features already there to build something up than to add new APIs that try to cover each of these special cases.

On Thu, Aug 17, 2017 at 8:40 AM Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
We might as well add the index to the call so elements can be created from other lists, etc.

Array(repeatedlyCalling: { (index:Int) in MyView( forIndex:index ) }, count: 3) // This might by syntactically correct...

On Aug 17, 2017, at 9:24 AM, Rob Mayoff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Alternatively, instead of replacing the current definition with an autoclosure version, we could leave the current version in place and add a version taking a function. This could be especially useful when you’ve defined a function like `makeMySpecialKindOfButton() -> UIButton` that does a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Also, for those of you here who haven't heard my previous rant on the subject, I dislike using map for generating values that don't depend on transforming a domain to a range. (It has been argued that `_ in` is mapping from `Void`, but I still dislike it immensely)

Here are the ways that I have approached this:

// Ugh
[UIView(), UIView(), UIView(), UIView(), UIView()]

// No:
let viewsA = Array(repeating: UIView(), count: 5)
// You end up with 5 of the same view

// Two statements that really should be one
var views: [UIView] =
for _ in 1 ... 5 { views.append(UIView()) }

// Wrong use of `map`, because it's mapping over `Void`
let viewsA = (1 ... 5).map({ _ in UIView() })

// You can introduce `collect` specifically for the `_ in` in case, matching other languages:
public func collect<T>(_ generator: () throws -> T) rethrows -> [T]

// I think these are just ugly
let viewsA__ = sequence(first: UIView(), next: { _ in UIView() }).lazy.prefix(5)
let viewsB__ = sequence(state: 5, next: { defer { $0 -= 1 }; $0 == 0 ? nil : UIView() }

// You can build an iterator

let labeler = AnyIterator({ return UILabel() })
let labels4 = Array(labeler.prefix(5))

// which lets you create multiple "slices" off the iterator

let randoms = AnyIterator({ return Int(arc4random_uniform(100) )})

print(Array(randoms.prefix(5)))
print(Array(randoms.prefix(5)))

// A little complex and I don't like making this `Int` based, because it pulls the semantics away from sequences/collections

extension Int {
    func of<T>(_ generator: @autoclosure () -> T) -> [T] {
        assert(self >= 0, "cannot generate negative-count collection")
        return (0 ..< self).map({ _ in generator() })
    }
}

5.of(UIView())

// Even worse
protocol Constructable {
    init()
}

extension UIView: Constructable {}

extension Int {
    func of<T: Constructable>(_ generator: @autoclosure () -> T) -> UnfoldSequence<T, Int> {
        assert(self > 0, "cannot generate negative-count collection")
        return sequence(state: self, next: { (state: inout Int) -> T? in
            defer { state -= 1 }; return state == 0 ? nil : T.init() })
    }
}

print(Array(5.of(UIView())))

// or

extension Int {
    func of<T>(_ generator: @escaping @autoclosure () -> T) -> LazyMapRandomAccessCollection<(CountableRange<Int>), T> {
        assert(self > 0, "cannot generate negative-count collection")
        return (0 ..< self).lazy.map({ _ in generator() })
    }
}

// where some people preferred calling this `elements`, for example `5.elements(of: UIView())`

// You can go Array:

extension Array {
    /// Creates a new array containing the specified number of values created by repeating a generating closure.
    ///
    /// - Parameters:
    /// - count: The number of times to apply the closure. `count` must be zero or greater.
    /// - generator: The closure to execute
    public init(count: Int, repeating: () -> Element) {
        precondition(count >= 0, "")
        self.init((1 ... count).map({ _ in repeating() }))
    }
}

let labels = Array(count: 4) { UILabel() }

// Or similarly

extension Array {
    init(repeat count: Int, byCalling generator: @autoclosure () -> Element) {
        self = (1 ... count).map({ _ in generator() })
    }
}

// From Oliver H:

extension Array {
  convenience init(count: Int, repeating: () -> Elements) {
    self = Array( (0..<count).map { _ in repeating() } )
  }
}

let views: [UIView] = Array(count: 5) { UIView(frame: .zero) }

// This one is from Soroush K. I think it's clever but I wouldn't really use it

func * <T>(generator: @autoclosure () -> T, n: Int) -> [T] {
  (0..<n).map({ _ in generator() }
}

UIView() * 5

···

On Aug 17, 2017, at 10:36 AM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

On Thu, Aug 17, 2017 at 9:20 AM Ross O'Brien <narrativium+swift@gmail.com <mailto:narrativium%2Bswift@gmail.com>> wrote:
(0..<3).map{ _ in UIView() } - map already returns an Array.

Array((0..<3).map{ _ in UIView() }) is redundant.

Ah, right, thanks for pointing that out. I couldn't remember off the top of my head whether it returned an array or some kind of special sequence type that would need to be converted over.

In that case, I still think the map version wins—it's very clear that a repeated *operation* is occurring, whereas the originally proposed @autoclosure version hides that very important semantic information.

I've fallen foul before, of trying to create an array of six buttons and getting an array of one button six times; I think that should be easier. But if each button corresponds to an existing value or needs to be initialised based on its index in that array, map transposing values or indices into buttons is already covered.

On Thu, Aug 17, 2017 at 5:03 PM, Tony Allevato via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Couldn't this be rewritten more simply today as:

    Array((0..<3).map { index in MyView(forIndex: index) })

And the version that doesn't need the index could be written:

    Array((0..<3).map { _ in UIView() })

The AnyIterator approach posted above is also nice—I wouldn't have thought of that one. But I suppose that only works in the case where you don't need the index.

So the question is, should we increase the API surface of Array for something that (IMO) is already fairly straightforward to do? The nice thing about the approaches above is that they're composable. Array doesn't have to make any assumptions about closures or index arguments to those closures; it just takes a sequence and we already have primitives to construct sequences of new objects using .map. Array(repeating:count:) and repeatedElement(_:count:) are nice when the value being repeated is fixed, but I think once you start getting into questions like "what if I need a different thing each time?" or "what if I need to involve the index as part of the elements?" then it's better suited to compose the features already there to build something up than to add new APIs that try to cover each of these special cases.

On Thu, Aug 17, 2017 at 8:40 AM Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
We might as well add the index to the call so elements can be created from other lists, etc.

Array(repeatedlyCalling: { (index:Int) in MyView( forIndex:index ) }, count: 3) // This might by syntactically correct...

On Aug 17, 2017, at 9:24 AM, Rob Mayoff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Alternatively, instead of replacing the current definition with an autoclosure version, we could leave the current version in place and add a version taking a function. This could be especially useful when you’ve defined a function like `makeMySpecialKindOfButton() -> UIButton` that does a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Summary:

. Since this application is a generator and not a transformative function, `map` is a misfit to usage semantics. It breaks the contract that map means to project from a domain to a range via a function. More languages conventionally use `collect` than `map` to collect n applications of a generator closure

-- E

···

On Aug 17, 2017, at 12:04 PM, Max Moiseev <moiseev@apple.com> wrote:

On Aug 17, 2017, at 10:05 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Also, for those of you here who haven't heard my previous rant on the subject, I dislike using map for generating values that don't depend on transforming a domain to a range. (It has been argued that `_ in` is mapping from `Void`, but I still dislike it immensely)

Can you please elaborate why (or maybe point me at the rant)?

(0..<3).map{ _ in UIView() } - map already returns an Array.

Array((0..<3).map{ _ in UIView() }) is redundant.

Ah, right, thanks for pointing that out. I couldn't remember off the top of
my head whether it returned an array or some kind of special sequence type
that would need to be converted over.

In that case, I still think the map version wins—it's very clear that a
repeated *operation* is occurring, whereas the originally proposed
@autoclosure version hides that very important semantic information.

I've fallen foul before, of trying to create an array of six buttons and

···

On Thu, Aug 17, 2017 at 9:20 AM Ross O'Brien <narrativium+swift@gmail.com> wrote:

getting an array of one button six times; I think that should be easier.
But if each button corresponds to an existing value or needs to be
initialised based on its index in that array, map transposing values or
indices into buttons is already covered.

On Thu, Aug 17, 2017 at 5:03 PM, Tony Allevato via swift-evolution < > swift-evolution@swift.org> wrote:

Couldn't this be rewritten more simply today as:

    Array((0..<3).map { index in MyView(forIndex: index) })

And the version that doesn't need the index could be written:

    Array((0..<3).map { _ in UIView() })

The AnyIterator approach posted above is also nice—I wouldn't have
thought of that one. But I suppose that only works in the case where you
don't need the index.

So the question is, should we increase the API surface of Array for
something that (IMO) is already fairly straightforward to do? The nice
thing about the approaches above is that they're composable. Array doesn't
have to make any assumptions about closures or index arguments to those
closures; it just takes a sequence and we already have primitives to
construct sequences of new objects using .map. Array(repeating:count:) and
repeatedElement(_:count:) are nice when the value being repeated is fixed,
but I think once you start getting into questions like "what if I need a
different thing each time?" or "what if I need to involve the index as part
of the elements?" then it's better suited to compose the features already
there to build something up than to add new APIs that try to cover each of
these special cases.

On Thu, Aug 17, 2017 at 8:40 AM Christopher Kornher via swift-evolution < >> swift-evolution@swift.org> wrote:

We might as well add the index to the call so elements can be created
from other lists, etc.

Array(repeatedlyCalling: { (index:Int) in MyView( forIndex:index ) },
count: 3) // This might by syntactically correct...

On Aug 17, 2017, at 9:24 AM, Rob Mayoff via swift-evolution < >>> swift-evolution@swift.org> wrote:

On Thu, Aug 17, 2017 at 7:04 AM, Robert Bennett via swift-evolution < >>> swift-evolution@swift.org> wrote:

Alternatively, instead of replacing the current definition with an
autoclosure version, we could leave the current version in place and add a
version taking a function. This could be especially useful when you’ve
defined a function like `makeMySpecialKindOfButton() -> UIButton` that does
a lot of customization to an object before returning it.

Array(repeating: { return UIView() }, count: 3)
and
Array(repeating: makeMySpecialKindOfButton, count: 3)

This is still source-breaking, because an array of closures is legal. We
need a different name to avoid breaking source:

    Array(repeatedlyCalling: { UIView() }, count: 3)

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

This is, I would argue, much too limiting in the way of semantics and not
at all required by “map”. It’s unclear to me how _any_ result with
reference semantics or any function with side effects could be used in a
way that comports with that definition.

On the other hand, just as y = 0x is a function, { _ in Foo() } is a
closure that very much does project from a domain to a range. I’m not sure
I understand what wins are to be had by having “collect {}” as a synonym
for “map { _ in }”.

···

On Thu, Aug 17, 2017 at 16:01 Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

On Aug 17, 2017, at 12:04 PM, Max Moiseev <moiseev@apple.com> wrote:

On Aug 17, 2017, at 10:05 AM, Erica Sadun via swift-evolution < > swift-evolution@swift.org> wrote:

Also, for those of you here who haven't heard my previous rant on the
subject, I dislike using map for generating values that don't depend on
transforming a domain to a range. (It has been argued that `_ in` is
mapping from `Void`, but I still dislike it immensely)

Can you please elaborate why (or maybe point me at the rant)?

Summary:

. Since this application is a generator and not a transformative function,
`map` is a misfit to usage semantics. It breaks the contract that map means
to project from a domain to a range via a function. More languages
conventionally use `collect` than `map` to collect n applications of a
generator closure

-- E

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

What if we got a duplication “macro”:

[ #dup(5 ; UIView()) ]

It’s not a true macro because it reads the count when “constexpr” objects would be evaluated.

···

On Aug 17, 2017, at 1:06 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

Also, for those of you here who haven't heard my previous rant on the subject, I dislike using map for generating values that don't depend on transforming a domain to a range. (It has been argued that `_ in` is mapping from `Void`, but I still dislike it immensely)

Here are the ways that I have approached this:

// Ugh
[UIView(), UIView(), UIView(), UIView(), UIView()]

—
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com

What people are doing is taking a real set of values (1, 2, 3, 4, 5, for example), then discarding them via `_ in`, which is different from `Void -> T` or `f(x) = 0 * x`. The domain could just as easily be (Foo(), "b", :poop:, UIColor.red, { x: Int in x^x }). There are too many semantic shifts away from "I would like to collect the execution of this closure n times" for it to sit comfortably.

-- E

···

On Aug 17, 2017, at 3:53 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

This is, I would argue, much too limiting in the way of semantics and not at all required by “map”. It’s unclear to me how _any_ result with reference semantics or any function with side effects could be used in a way that comports with that definition.

On the other hand, just as y = 0x is a function, { _ in Foo() } is a closure that very much does project from a domain to a range. I’m not sure I understand what wins are to be had by having “collect {}” as a synonym for “map { _ in }”.

On Thu, Aug 17, 2017 at 16:01 Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Aug 17, 2017, at 12:04 PM, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

On Aug 17, 2017, at 10:05 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Also, for those of you here who haven't heard my previous rant on the subject, I dislike using map for generating values that don't depend on transforming a domain to a range. (It has been argued that `_ in` is mapping from `Void`, but I still dislike it immensely)

Can you please elaborate why (or maybe point me at the rant)?

Summary:

. Since this application is a generator and not a transformative function, `map` is a misfit to usage semantics. It breaks the contract that map means to project from a domain to a range via a function. More languages conventionally use `collect` than `map` to collect n applications of a generator closure

-- E

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

1 Like