[swift-evolution] Swift 3 Ranges

Dave Abrahams dabrahams at apple.com
Wed Sep 14 11:33:25 CDT 2016


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

>> On 6 Sep 2016, at 07:39, David Hart via swift-evolution
> <swift-evolution at 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 at swift.org <mailto:swift-evolution at 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.
>
> 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:).

-- 
-Dave



More information about the swift-evolution mailing list