I'd like to propose going in a different direction than coming up with an operator that fakes a prefix -
. This syntax fools the eye but doesn't really express the desired semantics.
In the context of offsetting indices, 2..<-1
doesn't actually mean a negative range, because in this context we actually expect endIndex - 1
to come after (i.e., to be greater than) startIndex + 2
. I'd rather we made it possible to actually express the desired semantics:
@_fixed_layout
public enum IndexOffset : Equatable {
case start(Int)
case end(Int)
}
extension IndexOffset {
public init(_ source: Int) {
self = source < 0 ? .end(source) : .start(source)
}
}
extension IndexOffset : ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self.init(value)
}
}
extension IndexOffset : Comparable {
public static func < (lhs: IndexOffset, rhs: IndexOffset) -> Bool {
// Note how this comparison reflects our intended semantics.
switch (lhs, rhs) {
case (.start, .end): return true
case let (.start(a), .start(b)): return a < b
case (.end, .start): return false
case let (.end(a), .end(b)): return a < b
}
}
}
extension Collection {
internal func _index(_ offset: IndexOffset) -> Index {
switch offset {
case let .start(distance):
return index(startIndex, offsetBy: distance)
case let .end(distance):
return index(endIndex, offsetBy: distance)
}
}
public subscript(offset range: Range<IndexOffset>) -> SubSequence {
return self[_index(range.lowerBound)..<_index(range.upperBound)]
}
}
let x = [1, 2, 3, 4, 5]
x[offset: 1 ..< -1] // [2, 3, 4]
[Edited per Nevin's suggestion.]