Skip to content

Instantly share code, notes, and snippets.

@dabrahams
Created April 25, 2020 13:37
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dabrahams/852dfdb0b628e68567b4d97499f196f9 to your computer and use it in GitHub Desktop.
Save dabrahams/852dfdb0b628e68567b4d97499f196f9 to your computer and use it in GitHub Desktop.
Post-hoc specialized behavior based on refinement
/// 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