Last active
June 18, 2020 14:58
-
-
Save dabrahams/fef7cf8ff6946477ffecc129867c19e3 to your computer and use it in GitHub Desktop.
(https://bugs.swift.org/browse/SR-12692) Demonstrates that the current model for protocol extensions and conditional conformances is… unreasonable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| extension Sequence { | |
| var array: Array<Element> { | |
| print("preallocating", self.underestimatedCount) | |
| return Array(self) | |
| } | |
| } | |
| _ = Array(0..<1000).reversed().array // preallocating 1000 | |
| _ = repeatElement(1..<10, count: 200).joined().array // preallocating 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// The total number of single-hop index steps made in our test collections. | |
| var totalSteps = 0 | |
| /// A collection that tracks the number of single-hop index steps executed. | |
| struct CountSteps0 : RandomAccessCollection { | |
| init(count: Int) { self.count = count } | |
| var count: Int | |
| var startIndex: Int { 0 } | |
| var endIndex: Int { count } | |
| func index(after i: Int) -> Int { | |
| totalSteps += 1 | |
| return i + 1 | |
| } | |
| func index(before i: Int) -> Int { | |
| totalSteps += 1 | |
| return i - 1 | |
| } | |
| subscript(i: Int) -> Int { return i } | |
| } | |
| /// A collection that is conditionally Random Access | |
| struct CountSteps1<T: Collection> : Collection { | |
| init(count: Int) { self.count = count } | |
| var count: Int | |
| var startIndex: Int { 0 } | |
| var endIndex: Int { count } | |
| func index(after i: Int) -> Int { | |
| totalSteps += 1 | |
| return i + 1 | |
| } | |
| subscript(i: Int) -> Int { return i } | |
| } | |
| extension CountSteps1 | |
| : RandomAccessCollection, BidirectionalCollection | |
| where T : RandomAccessCollection | |
| { | |
| func index(before i: Int) -> Int { | |
| totalSteps += 1 | |
| return i - 1 | |
| } | |
| // Even if you don't take advantage of the easy defaults where Index == Int, | |
| // which you typically won't get because your Index will be based on T's index | |
| // type, it's broken. You can see this by uncommenting the two other | |
| // requirements you're supposed to implement if your collection is random | |
| // access. | |
| /* | |
| func index(_ i: Int, offsetBy d: Int) -> Int { | |
| return i + d | |
| } | |
| func distance(from i: Int, to j: Int) -> Int { | |
| return j - i | |
| } | |
| */ | |
| } | |
| extension Collection { | |
| /// Returns the 500th index position if it exists, or `nil` otherwise. | |
| /// | |
| /// Complexity: O(1) if `Self` conforms to `RandomAccessCollection`, O(N) | |
| /// otherwise. | |
| func midPoint() -> Index { | |
| index(startIndex, offsetBy: count / 2, limitedBy: endIndex)! | |
| } | |
| } | |
| // CountSteps0 picks up a number of defaults designed to make it easy to create | |
| // `RandomAccessCollection`s. | |
| let x0 = CountSteps0(count: 900) | |
| _ = x0.midPoint() | |
| print("steps:", totalSteps) // 0 | |
| /// CountSteps1 doesn't pick up any RandomAccessCollection default | |
| /// implementations, so the complexity guarantee of the `midPoint()` algorithm | |
| /// is broken. This is the case even if we fill in all the requirements you're | |
| /// supposed to have to include in a model of `RandomAccessCollection` | |
| /// (uncomment above). | |
| let x1 = CountSteps1<Range<Int>>(count: 900) | |
| _ = x1.midPoint() | |
| print("steps:", totalSteps) // 450 | |
| // Prove that x1 really is a RandomAccessCollection | |
| extension RandomAccessCollection { | |
| var isRandomAccess: Bool { true } | |
| } | |
| extension Collection { | |
| /// Returns `endIndex` offset by `n` positions. | |
| /// | |
| /// - Requires: if `Self` does not conform to `BidirectionalCollection`, `n == | |
| /// 0`. | |
| func offsetEnd(by n: Int) -> Index { | |
| index(endIndex, offsetBy: n) | |
| } | |
| } | |
| print(x1.isRandomAccess) | |
| _ = x1.offsetEnd(by: -1) // TRAP | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Just to see if I'm getting the point of the ConditionalConformanceOfGenerics.swift example.
So, while this example can be "fixed", ie fixed as in getting it to print:
by making some seemingly relevant modifications to the code, eg:
this is not fixing the underlying/general problem.
This "solution" is just treating some particular symptoms exposed by this specific example, and it does so by repeating code (ie giving up code reuse / the point of generic programming).
Right?