I remember being against this feature when it was first discussed long ago. But 
I’ve since appreciated how elegant it is. I also like the i… was chosen instead 
of i..<

I guess Range would be a better name for the generic protocol to represent all 
ranges. But its too late for that now. Correct?

> On 12 Apr 2017, at 18:40, Ben Cohen via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> Hi Swift community,
> 
> Another proposal pitch. These operators were mentioned briefly in the String 
> manifesto as prefixing/suffixing is very common with strings.
> 
> Online copy here: 
> https://github.com/airspeedswift/swift-evolution/blob/71b819d30676c44234bac1aa999961fc5c39bcf3/proposals/NNNN-OneSidedRanges.md
>  
> <https://github.com/airspeedswift/swift-evolution/blob/71b819d30676c44234bac1aa999961fc5c39bcf3/proposals/NNNN-OneSidedRanges.md>
> One-sided Ranges
> 
> Proposal: SE-NNNN 
> <file:///Users/ben_cohen/Documents/swift-evolution/proposals/NNNN-filename.md>
> Authors: Ben Cohen <https://github.com/airspeedswift>, Dave Abrahams 
> <https://github.com/dabrahams>, Brent Royal-Gordon 
> <https://github.com/brentdax>
> Review Manager: TBD
> Status: Awaiting review
> Introduction
> 
> This proposal introduces the concept of a “one-sided” range, created via 
> prefix/postfix versions of the existing range operators.
> 
> It also introduces a new protocol, RangeExpression, to simplify the creation 
> of methods that take different kinds of ranges.
> 
> Motivation
> 
> It is common, given an index into a collection, to want a slice up to or from 
> that index versus the start/end.
> 
> For example (assuming String is once more a Collection):
> 
> let s = "Hello, World!"
> let i = s.index(where: ",")
> let greeting = s[s.startIndex..<i]
> When performing lots of slicing like this, the verbosity of repeating 
> s.startIndex is tiresome to write and harmful to readability.
> 
> Swift 3’s solution to this is a family of methods:
> 
> let greeting = s.prefix(upTo: i)
> let withComma = s.prefix(through: i)
> let location = s.suffix(from: i)
> The two very different-looking ways to perform a similar task is jarring. And 
> as methods, the result cannot be used as an l-value.
> 
> A variant of the one-sided slicing syntax found in Python (i.e. s[i:]) is 
> proposed to resolve this.
> 
> Proposed solution
> 
> Introduce a one-sided range syntax, where the “missing” side is inferred to 
> be the start/end:
> 
> // half-open right-handed range
> let greeting = s[..<i]
> // closed right-handed range
> let withComma = s[...i]
> // left-handed range (no need for half-open variant)
> let location = s[i...]
> Additionally, when the index is a countable type, i... should form a Sequence 
> that counts up from i indefinitely. This is useful in forming variants of 
> Sequence.enumerated() when you either want them non-zero-based i.e. zip(1..., 
> greeting), or want to flip the order i.e. zip(greeting, 0...).
> 
> This syntax would supercede the existing prefix and suffix operations that 
> take indices, which will be deprecated in a later release. Note that the 
> versions that take distances are not covered by this proposal, and would 
> remain.
> 
> This will require the introduction of new range types (e.g. 
> PartialRangeThrough). There are already multiple range types (e.g. 
> ClosedRange, CountableHalfOpenRange), which require overloads to allow them 
> to be used whereever a Range can be.
> 
> To unify these different range types, a new protocol, RangeExpression will be 
> created and all ranges conformed to it. Existing overloads taking concrete 
> types other than Range can then be replaced with a single generic method that 
> takes a RangeExpression, converts it to a Range, and then forward the method 
> on.
> 
> A generic version of ~= will also be implemented for all range expressions:
> 
> switch i {
> case 9001...: print("It’s over NINE THOUSAAAAAAAND")
> default: print("There's no way that can be right!")
> }
> The existing concrete overloads that take ranges other than Range will be 
> deprecated in favor of generic ones that take a RangeExpression.
> 
> Detailed design
> 
> Add the following to the standard library:
> 
> (a fuller work-in-progress implementation can be found here: 
> https://github.com/apple/swift/pull/8710 
> <https://github.com/apple/swift/pull/8710>)
> 
> NOTE: The following is subject to change depending on pending compiler 
> features. Methods may actually be on underscored protocols, and then moved 
> once recursive protocols are implemented. Types may be collapsed using 
> conditional conformance. This should not matter from a usage perspective – 
> users are not expected to use these types directly or override any of the 
> behaviors in their own types. Any final implementation will follow the below 
> in spirit if not in practice.
> 
> public protocol RangeExpression {
>     associatedtype Bound: Comparable
> 
>     /// Returns `self` expressed as a range of indices within `collection`.
>     ///
>     /// -Parameter collection: The collection `self` should be
>     ///                        relative to.
>     ///
>     /// -Returns: A `Range<Bound>` suitable for slicing `collection`.
>     ///           The return value is *not* guaranteed to be inside
>     ///           its bounds. Callers should apply the same preconditions
>     ///           to the return value as they would to a range provided
>     ///           directly by the user.
>     func relative<C: _Indexable>(to collection: C) -> Range<Bound> where 
> C.Index == Bound
> 
>     func contains(_ element: Bound) -> Bool
> }
> 
> extension RangeExpression {
>   public static func ~= (pattern: Self, value: Bound) -> Bool
> }
> 
> prefix operator ..<
> public struct PartialRangeUpTo<T: Comparable>: RangeExpression {
>   public init(_ upperBound: T) { self.upperBound = upperBound }
>   public let upperBound: T
> }
> extension Comparable {
>   public static prefix func ..<(x: Self) -> PartialRangeUpTo<Self>
> }
> 
> prefix operator ...
> public struct PartialRangeThrough<T: Comparable>: RangeExpression {
>   public init(_ upperBound: T)
>   public let upperBound: T
> }
> extension Comparable {
>   public static prefix func ...(x: Self) -> PartialRangeThrough<Self>
> }
> 
> postfix operator ...
> public struct PartialRangeFrom<T: Comparable>: RangeExpression {
>   public init(_ lowerBound: T)
>   public let lowerBound: T
> }
> extension Comparable {
>   public static postfix func ...(x: Self) -> PartialRangeFrom<Self>
> }
> 
> // The below relies on Conditional Conformance. Pending that feature,
> // this may require an additional CountablePartialRangeFrom type temporarily.
> extension PartialRangeFrom: Sequence 
>   where Index: _Strideable, Index.Stride : SignedInteger
> 
> 
> extension Collection {
>   public subscript<R: RangeExpression>(r: R) -> SubSequence
>    where R.Bound == Index { get }
> }
> extension MutableCollection {
>   public subscript<R: RangeExpression>(r: R) -> SubSequence
>    where R.Bound == Index { get set }
> }
>   
> extension RangeReplaceableColleciton {
>   public mutating func replaceSubrange<C: Collection, R: RangeExpression>(
>     _ subrange: ${Range}<Index>, with newElements: C
>   ) where C.Iterator.Element == Iterator.Element, R.Bound == Index
> 
>   public mutating func removeSubrange<R: RangeExpression>(
>     _ subrange: ${Range}<Index>
>   ) where R.Bound == Index
> }
> Additionally, these new ranges will implement appropriate protocols such as 
> CustomStringConvertible.
> 
> It is important to note that these new methods and range types are extensions 
> only. They are not protocol requirements, as they should not need to be 
> customized for specific collections. They exist only as shorthand to expand 
> out to the full slicing operation.
> 
> The prefix and suffix methods that take an index are currently protocol 
> requirements, but should not be. This proposal will fix that as a side-effect.
> 
> Where PartialRangeFrom is a Sequence, it is left up to the type of Index to 
> control the behavior when the type is incremented past its bounds. In the 
> case of an Int, the iterator will trap when iterating past Int.max. Other 
> types, such as a BigInt that could be incremented indefinitely, would behave 
> differently.
> 
> Source compatibility
> 
> The new operators/types are purely additive so have no source compatibility 
> consequences. Replacing the overloads taking concrete ranges other than Range 
> with a single generic version is source compatible. prefix and suffix will be 
> deprecated in Swift 4 and later removed.
> 
> Effect on ABI stability
> 
> The prefix/suffix methods being deprecated should be eliminated before 
> declaring ABI stability.
> 
> Effect on API resilience
> 
> The new operators/types are purely additive so have no resilience 
> consequences.
> 
> Alternatives considered
> 
> i... is favored over i..< because the latter is ugly. We have to pick one, 
> two would be redundant and likely to cause confusion over which is the 
> “right” one. Either would be reasonable on pedantic correctness grounds – (i 
> as Int)... includes Int.max consistent with ..., whereas a[i...] is 
> interpreted as a[i..<a.endIndex] consistent with i..<.
> 
> It might be nice to consider extend this domain-specific language inside the 
> subscript in other ways. For example, to be able to incorporate the index 
> distance versions of prefix, or add distance offsets to the indices used 
> within the subscript. This proposal explicitly avoids proposals in this area. 
> Such ideas would be considerably more complex to implement, and would make a 
> good project for investigation by an interested community member, but would 
> not fit within the timeline for Swift 4.
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

Reply via email to