Created
April 25, 2020 13:37
-
-
Save dabrahams/852dfdb0b628e68567b4d97499f196f9 to your computer and use it in GitHub Desktop.
Post-hoc specialized behavior based on refinement
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
/// A repository of functionality depending on the conformances of `Model`. | |
/// | |
/// Conditional conformances provide implementation functions that take a | |
/// generic argument type with the safe assumption that the argument's concrete | |
/// type is `Model`. | |
struct Dispatch<Model> { | |
/// Returns `f(a as! Model) as! R1` | |
/// | |
/// Used by implementation functions to avoid the clutter of casting | |
/// explicitly. | |
func apply<A, R0, R1>(_ a: A, _ f: (Model)->R0) -> R1 { | |
f(a as! Model) as! R1 | |
} | |
} | |
/// Operations on signed numeric values. | |
/// | |
/// All operations can assume their arguments conform to `SignedNumeric`. | |
protocol SignedNumericDispatch { | |
/// Returns `-n`. | |
func negated<N>(_: N) -> N | |
} | |
extension Dispatch : SignedNumericDispatch where Model: SignedNumeric { | |
func negated<N>(_ x: N) -> N { apply(x, -) } | |
} | |
extension Numeric { | |
/// Returns `-self` if `Self` conforms to `SignedNumeric`, and `self` | |
/// otherwise. | |
var negatedIfSigned: Self { | |
(Dispatch<Self>() as? SignedNumericDispatch)?.negated(self) ?? self | |
} | |
} | |
// Tests | |
assert((1 as Int).negatedIfSigned == -1) | |
assert((1 as UInt).negatedIfSigned == 1) | |
assert((1 as Float).negatedIfSigned == -1) | |
// ---------------------------------------------------------------- | |
// Demonstrate use of an associated type on BidirectionalCollection | |
/// Operations on `BidirectionalCollection` values. | |
/// | |
/// All operations can assume their arguments conform to | |
/// `BidirectionalCollection`. | |
protocol BidirectionalCollectionDispatch { | |
/// Returns the index of the last element of `x`, or `nil` if `self.isEmpty`. | |
func lastIndex<C: Collection>(_ x: C) -> C.Index? | |
/// Returns the index before `i` in `x`. | |
func index<C: Collection>(_ x: C, before i: C.Index) -> C.Index | |
} | |
extension Dispatch : BidirectionalCollectionDispatch | |
where Model: BidirectionalCollection | |
{ | |
func lastIndex<C: Collection>(_ x: C) -> C.Index? { | |
apply(x) { $0.indices.last } | |
} | |
/// Returns the index before `i` in `x`. | |
func index<C: Collection>(_ x: C, before i: C.Index) -> C.Index { | |
apply(x) { $0.index(before: i as! Model.Index) } | |
} | |
} | |
extension Collection { | |
// Demonstrates how we can factor out some casting when there are multiple | |
// operations. | |
internal var bidirectionalDispatch: BidirectionalCollectionDispatch? { | |
Dispatch<Self>() as? BidirectionalCollectionDispatch | |
} | |
var lastIndexIfBidirectionalElseStart: Index? { | |
bidirectionalDispatch?.lastIndex(self) ?? self.startIndex | |
} | |
func indexIfBidirectional(before i: Index) -> Index? { | |
bidirectionalDispatch?.index(self, before: i) | |
} | |
} | |
// Tests | |
let s0 = 0...9 | |
let s1 = AnyCollection(s0) | |
assert(s0.lastIndexIfBidirectionalElseStart == s0.indices.last) | |
assert(s1.lastIndexIfBidirectionalElseStart == s1.startIndex) | |
assert(s0.indexIfBidirectional(before: s0.endIndex) == s0.indices.last) | |
assert(s1.indexIfBidirectional(before: s1.endIndex) == nil) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment