Created
February 11, 2018 21:30
-
-
Save anandabits/bd73521e0c5c06371f4a268ab8c482c9 to your computer and use it in GitHub Desktop.
Emulating closed protocols in Swift
This file contains 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
// This example shows how closed protocols can be emulated in Swift today. | |
// The technique leverages Swift's support for public (but not open) classes. | |
// First, it's worth observing that there is an almost trivial technique that can be used when | |
// it is possible to specify a (possibly abstract) superclass for all conforiming types. | |
// Simply declare a public (not open) base class and a protocol with a Self inheritance constraint. | |
// Swift does not support open subclasses of a public superclass so no classes outside the module | |
// will be able to meet the self inheritance constraint. | |
public class FooBase {} | |
public protocol Foo where Self: FooBase {} | |
// Now we can declare a set of conforming (and potentially non-conforming) subclasses that cannot be extended outside the module. | |
public class Foo1: FooBase, Foo {} | |
private class Foo2: FooBase, Foo {} | |
public class Foo3: FooBase {} | |
func takesFoo<F: Foo>(_ foo: F) { | |
switch F.self { | |
case is Foo1.Type: print("foo1") | |
case is Foo2.Type: print("foo2") | |
default: fatalError("unfortunately the exhaustiveness checker is not as smart as we would like it to be.") | |
} | |
} | |
takesFoo(Foo1()) // "foo1" | |
takesFoo(Foo2()) // "foo2" | |
// This is a nice technique but it is also very limited. | |
// Fortunately, it can be leveraged to support arbitrary closed protocols | |
// at the cost of an associated type in the "closed" protocol | |
// and a set of classes parallel to the conforming types. | |
/// A non-open base class for classes that run parallel to the types conforming to a closed protocol. | |
/// | |
/// The _ in the name indicates that while this class must be public it is an implementation detail | |
/// that should be ignored by users of the module. | |
public class _BarValidatorBase {} | |
/// A protocol with a Self inheritance constraint requiring conforming types to inherit from `_BarValidatorBase`. | |
/// Classes which conform to this protocol validate that a specified type is allowed to conform to the protocol. | |
/// | |
/// The _ in the name indicates that while this class must be public it is an implementation detail | |
/// that should be ignored by users of the module. | |
public protocol _BarValidator where Self: _BarValidatorBase { | |
/// A type that is allowed to conform to the "closed" protocol. | |
associatedtype Validated | |
} | |
/// A protocol for which no conformances are possible outside the declaring module. | |
/* closed */ public protocol Bar { | |
/// An associated type requiring the presence of a class that "validates" that a type is allowed to conform to `Bar`. | |
/// | |
/// The _ in the name indicates that while this class must be public it is an implementation detail | |
/// that should be ignored by users of the module. | |
/// | |
/// Unfortunately, the presence of this associated type precludes the use of this protocol | |
/// as an existential type until support for generalized existentials are added to Swift. | |
/// The good news is that when such support is added it should be possible to use the protocol as if this associated type | |
/// did not exist at all (aside from the obvious need to specify it in conformances provided by the module). | |
associatedtype _Validator: _BarValidator where _Validator.Validated == Self | |
} | |
/// A struct that conforms to the "closed" protocol. | |
public struct Bar1: Bar { | |
/// In order to conform, `Bar1` is required to specify a `_Validator` whose `Validated == Bar1`. | |
/// A nested class is the simplest way to do this. | |
public class _Validator: _BarValidatorBase, _BarValidator { | |
public typealias Validated = Bar1 | |
} | |
} | |
/// A generic struct that conforms to the "closed" protocol. | |
public struct GenericBar<T>: Bar { | |
/// As with a non-generic type, a nested validator is used to validate the conformance. | |
public class _Validator: _BarValidatorBase, _BarValidator { | |
public typealias Validated = GenericBar<T> | |
} | |
} | |
/// Classes can of course also conform to this protocol if desired. | |
public class BarClass: Bar { | |
/// As with a non-generic type, a nested validator is used to validate the conformance. | |
public class _Validator: _BarValidatorBase, _BarValidator { | |
public typealias Validated = BarClass | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment