I've written up a draft proposal for this below. The number of methods I've had to add is unfortunate (see 'Detail Design'). If anyone one has ideas on how to improve this please let me know. Another thought, since IndexDistance
is going to be Int
, should this only support Countable*
ranges and PartialRange(UpTo|Through)
?
Offset Range Subscript
Introduction
A collection that has an Index
type that cannot be offset independently of its collection can cause overly verbose code that obfuscates one's intent. To help improve this we propose adding subscript(offset:)
methods to Collection
and MutableCollection
that would accept an offsetting range.
Swift-evolution thread: Discussion thread topic for that proposal
Motivation
Working with an index that cannot be offset independently, without its corresponding collection, causes the intent of code to get lost in an overly verbose call site.
As an example; to get a slice of a String
, not anchored at the start or end of the collection, one would use the following subscript method:
let s = "Hello, Swift!"
let subject = s[s.index(s.startIndex, offsetBy: 7)...s.index(s.startIndex, offsetBy: 11)]
Proposed solution
A solution we propose to this problem is to extend Collection
and MutableCollection
with subscript methods that take ranges which would be used to offset the starting index of a collection.
Using the above example, along with our solution, we will be able to write the following.
let subject = s[offset: 7...11]
Detailed design
Extend Collection
with implementations of the methods listed below, as well as, add implementations of getter/setter variants to MutableCollection
.
subscript(offset offset: ClosedRange<IndexDistance>) -> SubSequence {}
subscript(offset offset: Range<IndexDistance>) -> SubSequence {}
subscript(offset offset: PartialRangeFrom<IndexDistance>) -> SubSequence {}
subscript(offset offset: PartialRangeThrough<IndexDistance>) -> SubSequence {}
subscript(offset offset: PartialRangeUpTo<IndexDistance>) -> SubSequence {}
subscript(offset offset: CountablePartialRangeFrom<IndexDistance>) -> SubSequence {}
subscript(offset offset: CountableClosedRange<IndexDistance>) -> SubSequence {}
subscript(offset offset: CountableRange<IndexDistance>) -> SubSequence {}
Source compatibility
None
Effect on ABI stability
N/A
Effect on API resilience
N/A
Alternatives considered
Add methods to offset startIndex and/or endIndex
Adding convenience methods to offset startIndex
and endIndex
would help make intent more obvious, however, it still is not ideal.
let subject = s[s.startIndex(offsetBy: 7)...s.endIndex(offsetBy: -2)]
Only add a method to offset startIndex
If we were to include only a startIndex(offsetBy:)
we might want to reconsider a rename. One suggested name was index(atOffset:)
.
Use a KeyPath
Add an index(_:offsetBy:)
method that would take a KeyPath as its first argument. This will give us the following usage.
let subject = s[s.index(\.startIndex, offsetBy: 7)..<s.index(\.endIndex, offsetBy: -1)]
While this will shorten code, when the collection instance name is long, it is still relatively verbose.