Barring any new concrete use cases from the community, I think we should explore the longer-term solution with a DSL for constructing offset ranges.
Coming back around to this. With ABI and source stability, what can we realistically achieve here? We can't change RangeExpression itself or have it inherit from a more general protocol. And we can’t have it retroactively conform to a new protocol.
RangeExpression is currently:
public protocol RangeExpression {
associatedtype Bound: Comparable
func relative<C: Collection>(
to collection: C
) -> Range<Bound> where C.Index == Bound
func contains(_ element: Bound) -> Bool
The only users of RangeExpressions in the standard library are Collection, MutableCollection, and RangeReplaceableCollection. These are versions of subscript, replaceSubrange, and removeSubrange defined in extensions that are generic over RangeExpression and just pass it through relative(to:).
RangeExpression itself provides ~=, which uses contains. I don’t see anything using the Comparable constraint on the Bound associated type and there’s no default definition for contains, but maybe I missing something there. There is a TODO on Range.overlaps to put it on RangeExpression, but I don’t know how we’d provide a default implementation for an any-to-any overlap check since we don't know all potential conformers.
So, perhaps something like:
public protocol RelativeRangeExpression {
func relative<C: Collection>(to collection: C) -> Range<C.Index>
}
And we have 4 new overloads in extensions for RelativeRangeExpression? What would be the advantage of this protocol over just using a concrete type such as @beccadax's RelativeRange?
enum RelativeBound<Bound: Comparable> {
case from(Bound, offset: Int)
case fromStart(offset: Int)
case fromEnd(offset: Int)
}
struct RelativeRange<Bound: Comparable> {
let lowerBound: RelativeBound<Bound>
let upperBound: RelativeBound<Bound>
}
These offsets are relative to other indices (by default, start/endIndex). So this would read idx1[++|--]n < idx2[++|--]m iff idx1 < idx2.
Hole-in-the-middle is interesting, but what does this actually allow us to express? Or is this a technique for conforming to the existing RangeExpression?
I assume you mean relative(to:) in these responses (which is the one that takes a Collection).
Why do we need an untyped ++1...--2? In RelativeRange above, we'd require type context for the pure-offset representation (effectively phantom-typed).