Swift 3 Ranges


(David Hart) #1

Hi people,

I’ve recently started migrating some Swift 2 projects to Swift 3. I came across the split of Range into Range and ClosedRange and I’ve really struggled with it. Specifically, in Swift 2, I had a struct with a Range property that was initialised in many places with either a closed or open range:

struct Day { … }
struct Day : Comparable { … }
struct Day : Strippable { … }

struct Info {
    let name: String
    let range: Range<Day>
}

Info(name: "Christmas Vacation", range: twentyfith...thirtyfirst)
Info(name: "Summer Vacation", range: someday..<otherday)

Now, in Swift 3, it seems like we’ve lost a type to represent any range to allow an API client the flexibility to specify it as he wishes. Is there a solution to this problem through a protocol which both ranges conform to, or are we stuck with this because of the new API?

protocol RangeType {
    associatedtype Bounds
    let lowerBound: Bound { get }
    let upperBound: Bound { get }
    // what else? not even sure if it is possible to define such a protocol
}

David.


(David Hart) #2

I have the impression we exchanged flexibility for correctness (the ability to represent 0..<Int.max) and that it's wasn't worth the loss of flexibility.1

Or am I missing something?

···

On 6 Sep 2016, at 08:15, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

Hi people,

I’ve recently started migrating some Swift 2 projects to Swift 3. I came across the split of Range into Range and ClosedRange and I’ve really struggled with it. Specifically, in Swift 2, I had a struct with a Range property that was initialised in many places with either a closed or open range:

struct Day { … }
struct Day : Comparable { … }
struct Day : Strippable { … }

struct Info {
    let name: String
    let range: Range<Day>
}

Info(name: "Christmas Vacation", range: twentyfith...thirtyfirst)
Info(name: "Summer Vacation", range: someday..<otherday)

Now, in Swift 3, it seems like we’ve lost a type to represent any range to allow an API client the flexibility to specify it as he wishes. Is there a solution to this problem through a protocol which both ranges conform to, or are we stuck with this because of the new API?

protocol RangeType {
    associatedtype Bounds
    let lowerBound: Bound { get }
    let upperBound: Bound { get }
    // what else? not even sure if it is possible to define such a protocol
}

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


(Haravikk) #3

The problem was that to implement both generically, a closed range was just an open range with the upper bound increment by one step, but as you say this wasn't safe as Int.max etc. could not be used.

I think if you wanted to solve this you'd need to reintroduce the concept of incrementing by a minimum step, which was moved away from index types during the changes to the new indexing model for collections. You could do this something like so:

// Reintroduce these methods that were lost in the new indexing API
protocol ForwardStep { func successor()? -> Self }
protocol BackwardStep : ForwardStep { func predecessor()? -> Self }

// Enable conversion of ranges
extend ClosedRange where Self.Bound : ForwardStep {
  func toOpenRange() -> Range<Self.Bound>? {
    guard let upperBound = self.upperBound.successor() else { return nil }
    return self.lowerBound ..< upperBound
  }
}
extend Range where Self.Bound : BackwardStep {
  func toClosedRange() -> ClosedRange<Self.Bound>? {
    guard let upperBound = self.upperBound.predecessor() else { return nil }
    return self.lowerBound ... upperBound
  }
}
extend ClosedRange where Self.Bound : BackwardStep {
  init(_ openRange:Range<Self.Bound>)? {
    guard let closedRange = openRange.toClosedRange() else { return nil }
    self = closedRange
  }
}
extend Range where Self.Bound : ForwardStep {
  init(_ closedRange:Range<Self.Bound>)? {
    guard let openRange = closedRange.toClosedRange() else { return nil }
    self = openRange
  }
}

I've rushed this a bit so forgive any glaring errors, but this is essentially how I'd bolt this on right now myself. Basically it reintroduces some of the flexibility, but with the safety of optionals to avoid the previous problem of incrementing a maximum value, this shouldn't be a problem for performance since it's just being used for conversion.

···

On 6 Sep 2016, at 07:39, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

I have the impression we exchanged flexibility for correctness (the ability to represent 0..<Int.max) and that it's wasn't worth the loss of flexibility.1

Or am I missing something?

On 6 Sep 2016, at 08:15, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi people,

I’ve recently started migrating some Swift 2 projects to Swift 3. I came across the split of Range into Range and ClosedRange and I’ve really struggled with it. Specifically, in Swift 2, I had a struct with a Range property that was initialised in many places with either a closed or open range:

struct Day { … }
struct Day : Comparable { … }
struct Day : Strippable { … }

struct Info {
    let name: String
    let range: Range<Day>
}

Info(name: "Christmas Vacation", range: twentyfith...thirtyfirst)
Info(name: "Summer Vacation", range: someday..<otherday)

Now, in Swift 3, it seems like we’ve lost a type to represent any range to allow an API client the flexibility to specify it as he wishes. Is there a solution to this problem through a protocol which both ranges conform to, or are we stuck with this because of the new API?

protocol RangeType {
    associatedtype Bounds
    let lowerBound: Bound { get }
    let upperBound: Bound { get }
    // what else? not even sure if it is possible to define such a protocol
}

David.


(Dave Abrahams) #4

I have the impression we exchanged flexibility for correctness (the

ability to represent 0..<Int.max) and that it's wasn't worth the loss
of flexibility.1

Or am I missing something?

Hi people,

I’ve recently started migrating some Swift 2 projects to Swift 3. I
came across the split of Range into Range and ClosedRange and I’ve
really struggled with it. Specifically, in Swift 2, I had a struct
with a Range property that was initialised in many places with
either a closed or open range:

struct Day { … }
struct Day : Comparable { … }
struct Day : Strippable { … }

struct Info {
    let name: String
    let range: Range<Day>
}

Info(name: "Christmas Vacation", range: twentyfith...thirtyfirst)
Info(name: "Summer Vacation", range: someday..<otherday)

Now, in Swift 3, it seems like we’ve lost a type to represent any
range to allow an API client the flexibility to specify it as he
wishes. Is there a solution to this problem through a protocol
which both ranges conform to, or are we stuck with this because of
the new API?

protocol RangeType {
    associatedtype Bounds
    let lowerBound: Bound { get }
    let upperBound: Bound { get }
// what else? not even sure if it is possible to define such a

protocol

}

David.

The problem was that to implement both generically, a closed range was
just an open range with the upper bound increment by one step, but as
you say this wasn't safe as Int.max etc. could not be used.

It was safe. Trapping is safe behavior. It was simply not expressive
enough to express the ranges people wanted to write, e.g. 0...Int8.max.
Your scheme (below) isn't either.

I think if you wanted to solve this you'd need to reintroduce the
concept of incrementing by a minimum step, which was moved away from
index types during the changes to the new indexing model for
collections. You could do this something like so:

// Reintroduce these methods that were lost in the new indexing API
protocol ForwardStep { func successor()? -> Self }
protocol BackwardStep : ForwardStep { func predecessor()? -> Self }

// Enable conversion of ranges
extend ClosedRange where Self.Bound : ForwardStep {
  func toOpenRange() -> Range<Self.Bound>? {
    guard let upperBound = self.upperBound.successor()
else { return nil }
    return self.lowerBound ..< upperBound
  }
}
extend Range where Self.Bound : BackwardStep {
  func toClosedRange() -> ClosedRange<Self.Bound>? {
    guard let upperBound = self.upperBound.predecessor()
else { return nil }
    return self.lowerBound ... upperBound
  }
}
extend ClosedRange where Self.Bound : BackwardStep {
  init(_ openRange:Range<Self.Bound>)? {
    guard let closedRange = openRange.toClosedRange() else
{ return nil }
    self = closedRange
  }
}
extend Range where Self.Bound : ForwardStep {
  init(_ closedRange:Range<Self.Bound>)? {
    guard let openRange = closedRange.toClosedRange() else
{ return nil }
    self = openRange
  }
}

This would not result in correct range conversion with respect to all
collections. For example, a StridedCollection might step its
ForwardStep indices several times for each call to index(after:).

···

on Tue Sep 06 2016, Haravikk <swift-evolution@swift.org> wrote:

On 6 Sep 2016, at 07:39, David Hart via swift-evolution > <swift-evolution@swift.org> wrote:
On 6 Sep 2016, at 08:15, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

--
-Dave