Discussion: Move range (..., ..<) to a range() function


(Kyle Bashour) #1

Based on much of the discussion regarding removing C-style for-loops, I'd
like to propose a discussion on revamping how range works in Swift. The
lack of a reverse range operator and the fact and the range operator and
stride() seem to do a lot of the same work have made me wonder why there
isn't merely a range() function, as in Python.

I believe this would be easier for newcomers to learn, remove the need for
stride() (though there are probably use cases for stride() I don't know
about, I haven't used it too much), and actually be more clear than ..< and
...

Here are some examples of how it could work:

range(10) // equivalent to 0..<10
range(-1, to: 10) // equivalent to -1..<10
range(10, through: 0) equivalent to (0...10).reverse()
range(0, through: 10, by: 2) // equivalent to 0.stride(through: 10, by: 2)

Or, to avoid a global function, .range() should probably be a function like
stride, but with more features (equivalent to above)

10.range()
-1.range(to: 10)
10.range(through: 0)
0.range(through: 10, by: 2)

Would love thoughts on why this is good or bad, and if it's worth creating
an actual proposal.

Regards,

Kyle


(Austin Zheng) #2

... and ..< aren't privileged by the language in any way; they're equivalent to calling Range(start: x, end: y). It would be nice to add more convenience initializers to Range, but I don't want to see the operators go, and I don't think a free function is the best interface.

Austin

···

On Dec 8, 2015, at 11:01 AM, Kyle Bashour via swift-evolution <swift-evolution@swift.org> wrote:

Based on much of the discussion regarding removing C-style for-loops, I'd like to propose a discussion on revamping how range works in Swift. The lack of a reverse range operator and the fact and the range operator and stride() seem to do a lot of the same work have made me wonder why there isn't merely a range() function, as in Python.

I believe this would be easier for newcomers to learn, remove the need for stride() (though there are probably use cases for stride() I don't know about, I haven't used it too much), and actually be more clear than ..< and ...

Here are some examples of how it could work:

range(10) // equivalent to 0..<10
range(-1, to: 10) // equivalent to -1..<10
range(10, through: 0) equivalent to (0...10).reverse()
range(0, through: 10, by: 2) // equivalent to 0.stride(through: 10, by: 2)

Or, to avoid a global function, .range() should probably be a function like stride, but with more features (equivalent to above)

10.range()
-1.range(to: 10)
10.range(through: 0)
0.range(through: 10, by: 2)

Would love thoughts on why this is good or bad, and if it's worth creating an actual proposal.

Regards,

Kyle

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


(Brent Royal-Gordon) #3

Based on much of the discussion regarding removing C-style for-loops, I'd like to propose a discussion on revamping how range works in Swift. The lack of a reverse range operator and the fact and the range operator and stride() seem to do a lot of the same work have made me wonder why there isn't merely a range() function, as in Python.

I think the … and ..< operators are a great, concise way to specify the bounds of an operation.

However, that doesn’t mean this couldn’t use some work. Suppose we removed the Range-producing … and ..< operators, so that instead they always produce an IntervalType. Then we rename and rejigger Stride to work with intervals (remember, Strideable implies Comparable, and Comparable implies compatibility with IntervalType):

  struct Series<Bounds: IntervalType where Bounds.Bound: Strideable>: SequenceType {
    init(_ bounds: Bounds, by: Bounds.Bound.Stride) {
      …
    }

    …
  }
  extension Series where Bounds.Bound: DefaultStrideable {
    init(_ bounds: Bounds) {
      self.init(bounds, by: Bounds.Bound.defaultStride)
    }
  }
  protocol DefaultStrideable: Strideable {
    static var defaultStride: Self { get }
  }

The Strideable and new DefaultStrideable protocols would have to be renamed, of course; this is just a sketch.

Now your ordinary for loop looks like:

  for i in Series(1..<10) {
    ...
  }

And it’s easy to reverse it:

  for i in Series(1..<10).reverse() {
    …
  }

I’m not totally convinced this is a good idea—it makes the common count-up-by-one case more difficult—but if you’re going to redesign things, I think this is a better way to do it.

···

--
Brent Royal-Gordon
Architechies


(Donnacha Oisín Kidney) #4

Personally, I think that the range operator is easier to understand than a free range function. People who already program might find it easier to understand a free function rather than ..<, which looks like special syntax, but I think beginners (especially in Python, actually) are really floored by range functions. Take, for example:

range(5)

To someone unfamiliar with the function, it could (quite reasonably) mean:

1, 2, 3, 4, 5
0, 1, 2, 3, 4, 5
0, 1, 2, 3, 4
None of the above

In fact, for people unfamiliar with programming, I’d say that the correct answer is the least obvious. Compare that to:

0..<5

or:

0...4

I think the operator is much easier to understand, and more than makes up for the disadvantages associated with extra operators.

That said, I do think that Strideable is a little unclear. I can’t think of a much better option, though. Maybe something like:

extension Range where Element: Strideable {
  public func by(n: Element.Stride) -> StrideTo<Element> {
    return startIndex.stride(to: endIndex, by: n)
  }
}
(0..<10).by(2) // [0, 2, 4, 6, 8]

But I’d also want that to work as a subscript.

···

On 8 Dec 2015, at 19:01, Kyle Bashour via swift-evolution <swift-evolution@swift.org> wrote:

Based on much of the discussion regarding removing C-style for-loops, I'd like to propose a discussion on revamping how range works in Swift. The lack of a reverse range operator and the fact and the range operator and stride() seem to do a lot of the same work have made me wonder why there isn't merely a range() function, as in Python.

I believe this would be easier for newcomers to learn, remove the need for stride() (though there are probably use cases for stride() I don't know about, I haven't used it too much), and actually be more clear than ..< and ...

Here are some examples of how it could work:

range(10) // equivalent to 0..<10
range(-1, to: 10) // equivalent to -1..<10
range(10, through: 0) equivalent to (0...10).reverse()
range(0, through: 10, by: 2) // equivalent to 0.stride(through: 10, by: 2)

Or, to avoid a global function, .range() should probably be a function like stride, but with more features (equivalent to above)

10.range()
-1.range(to: 10)
10.range(through: 0)
0.range(through: 10, by: 2)

Would love thoughts on why this is good or bad, and if it's worth creating an actual proposal.

Regards,

Kyle

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


(Jordan Rose) #5

Don't forget that most Ranges are on collection indexes, not strideable types, and Int is used as a collection index.

Jordan

···

On Dec 8, 2015, at 14:27, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Based on much of the discussion regarding removing C-style for-loops, I'd like to propose a discussion on revamping how range works in Swift. The lack of a reverse range operator and the fact and the range operator and stride() seem to do a lot of the same work have made me wonder why there isn't merely a range() function, as in Python.

I think the … and ..< operators are a great, concise way to specify the bounds of an operation.

However, that doesn’t mean this couldn’t use some work. Suppose we removed the Range-producing … and ..< operators, so that instead they always produce an IntervalType. Then we rename and rejigger Stride to work with intervals (remember, Strideable implies Comparable, and Comparable implies compatibility with IntervalType):

  struct Series<Bounds: IntervalType where Bounds.Bound: Strideable>: SequenceType {
    init(_ bounds: Bounds, by: Bounds.Bound.Stride) {
      …
    }

    …
  }
  extension Series where Bounds.Bound: DefaultStrideable {
    init(_ bounds: Bounds) {
      self.init(bounds, by: Bounds.Bound.defaultStride)
    }
  }
  protocol DefaultStrideable: Strideable {
    static var defaultStride: Self { get }
  }

The Strideable and new DefaultStrideable protocols would have to be renamed, of course; this is just a sketch.

Now your ordinary for loop looks like:

  for i in Series(1..<10) {
    ...
  }

And it’s easy to reverse it:

  for i in Series(1..<10).reverse() {
    …
  }

I’m not totally convinced this is a good idea—it makes the common count-up-by-one case more difficult—but if you’re going to redesign things, I think this is a better way to do it.


(Brent Royal-Gordon) #6

Don't forget that most Ranges are on collection indexes, not strideable types, and Int is used as a collection index.

In this design, I think Range<Index> is replaced by Interval: IntervalType where Interval.Bound == Index.

···

--
Brent Royal-Gordon
Architechies


(Jordan Rose) #7

Not all Index types are Comparable (just like not all Comparable or Strideable types are ForwardIndex types).

Jordan

···

On Dec 8, 2015, at 16:58, Brent Royal-Gordon <brent@architechies.com> wrote:

Don't forget that most Ranges are on collection indexes, not strideable types, and Int is used as a collection index.

In this design, I think Range<Index> is replaced by Interval: IntervalType where Interval.Bound == Index.


(Brent Royal-Gordon) #8

In this design, I think Range<Index> is replaced by Interval: IntervalType where Interval.Bound == Index.

Not all Index types are Comparable (just like not all Comparable or Strideable types are ForwardIndex types).

You’re right. Hmm, that is a pickle.

···

--
Brent Royal-Gordon
Architechies