[swift-evolution] [Manifesto] Completing Generics

Dave Abrahams dabrahams at apple.com
Thu Mar 3 18:22:01 CST 2016


on Wed Mar 02 2016, Douglas Gregor <swift-evolution at swift.org> wrote:

> This message expands upon the notion of “completing generics”. It is
> not a plan for Swift 3, nor an official core team communication, but
> it collects the results of numerous discussions among the core team
> and Swift developers, both of the compiler and the standard library. I
> hope to achieve several things:
>
>   * Communicate a vision for Swift generics, building on the original
>     generics design document, so we have something concrete and
>     comprehensive to discuss.

One thing I'd like to see in here, that I don't, is something about
where generics are supposed to fit into the performance model.  In
particular, with the advent of resilience and the coming end of
pervasive automatic inlining and specialization, I worry that generics
will not continue to be a viable way to write highly generalized code
that is also high-performance.  I think I know some ways we can avoid
this fate, but as far as I know we have no plans in place to do so.

>   * Establish some terminology that the Swift developers have been
>     using for these features, so our discussions can be more productive
>     (“oh, you’re proposing what we refer to as ‘conditional
>     conformances’; go look over at this thread”).
>
>   * Engage more of the community in discussions of specific generics
>     features, so we can coalesce around designs for public review. And
>     maybe even get some of them implemented.
>
> A message like this can easily turn into a centithread. To separate
> concerns in our discussion, I ask that replies to this specific thread
> be limited to discussions of the vision as a whole: how the pieces fit
> together, what pieces are missing, whether this is the right long-term
> vision for Swift, and so on. For discussions of specific language
> features, e.g., to work out the syntax and semantics of conditional
> conformances or discuss the implementation in compiler or use in the
> standard library, please start a new thread based on the feature names
> I’m using.
>
> This message covers a lot of ground; I’ve attempted a rough
> categorization of the various features, and kept the descriptions
> brief to limit the overall length. Most of these aren’t my ideas, and
> any syntax I’m providing is simply a way to express these ideas in
> code and is subject to change. Not all of these features will happen,
> either soon or ever, but they are intended to be a fairly complete
> whole that should mesh together. I’ve put a * next to features that I
> think are important in the nearer term vs. being interesting “some
> day”. Mostly, the *’s reflect features that will have a significant
> impact on the Swift standard library’s design and implementation.
>
> Enough with the disclaimers; it’s time to talk features.
>
> Removing unnecessary restrictions
>
> There are a number of restrictions to the use of generics that fall
> out of the implementation in the Swift compiler.  Removal of these
> restrictions is a matter of implementation only; one need not
> introduce new syntax or semantics to realize them. I’m listing them
> for two reasons: first, it’s an acknowledgment that these features are
> intended to exist in the model we have today, and, second, we’d love
> help with the implementation of these features.
>
> *Recursive protocol constraints
>
> Currently, an associated type cannot be required to conform to its
> enclosing protocol (or any protocol that inherits that protocol). For
> example, in the standard library SubSequence type of a Sequence should
> itself be a Sequence:
>
>     protocol Sequence {
>       associatedtype Iterator : IteratorProtocol
>>       associatedtype SubSequence : Sequence   // currently ill-formed, but should be possible
>     }
>
> The compiler currently rejects this protocol, which is unfortunate: it
> effectively pushes the SubSequence-must-be-a-Sequence requirement into
> every consumer of SubSequence, and does not communicate the intent of
> this abstraction well.
>
> Nested generics
>
> Currently, a generic type cannot be nested within another generic type, e.g.
>
>     struct X<T> {
>       struct Y<U> { }  // currently ill-formed, but should be possible
>     }
>
> There isn’t much to say about this: the compiler simply needs to be
> improved to handle nested generics throughout.
>
> Concrete same-type requirements
>
> Currently, a constrained extension cannot use a same-type constraint
> to make a type parameter equivalent to a concrete type. For example:
>
>     extension Array where Element == String {
>       func makeSentence() -> String {
>         // uppercase first string, concatenate with spaces, add a period, whatever
>       }
>     }
>
> This is a highly-requested feature that fits into the existing syntax
> and semantics. Note that one could imagine introducing new syntax,
> e.g., extending “Array<String>”, which gets into new-feature
> territory: see the section on “Parameterized extensions”.
>
> Parameterizing other declarations
>
> There are a number of Swift declarations that currently cannot have
> generic parameters; some of those have fairly natural extensions to
> generic forms that maintain their current syntax and semantics, but
> become more powerful when made generic.
>
> Generic typealiases
>
> Typealiases could be allowed to carry generic parameters. They would
> still be aliases (i.e., they would not introduce new types). For
> example:
>
>     typealias StringDictionary<Value> = Dictionary<String, Value>
>    
>     var d1 = StringDictionary<Int>()
>     var d2: Dictionary<String, Int> = d1 // okay: d1 and d2 have the same type, Dictionary<String, Int>
>
> Generic subscripts
>
> Subscripts could be allowed to have generic parameters. For example,
> we could introduce a generic subscript on a Collection that allows us
> to pull out the values at an arbitrary set of indices:
>
>     extension Collection {
>       subscript<Indices: Sequence where Indices.Iterator.Element == Index>(indices: Indices) -> [Iterator.Element] {
>         get {
>           var result = [Iterator.Element]()
>           for index in indices {
>             result.append(self[index])
>           }
>    
>           return result
>         }
>    
>         set {
>           for (index, value) in zip(indices, newValue) {
>             self[index] = value
>           }
>         }
>       }
>     }
>
> Generic constants
>
> let constants could be allowed to have generic parameters, such that
> they produce differently-typed values depending on how they are
> used. For example, this is particularly useful for named literal
> values, e.g.,
>
>     let π<T : FloatLiteralConvertible>: T = 3.141592653589793238462643383279502884197169399
>
> The Clang importer could make particularly good use of this when
> importing macros.
>
> Parameterized extensions

Aren't you missing a '*' on this one?

> Extensions themselves could be parameterized, which would allow some
> structural pattern matching on types. For example, this would permit
> one to extend an array of optional values, e.g.,
>
>     extension<T> Array where Element == T? {
>       var someValues: [T] {
>         var result = [T]()
>         for opt in self {
>           if let value = opt { result.append(value) }
>         }
>        return result
>       }
>     }
>
> We can generalize this to a protocol extensions:
>
>     extension<T> Sequence where Element == T? {
>       var someValues: [T] {
>         var result = [T]()
>         for opt in self {
>           if let value = opt { result.append(value) }
>         }
>        return result
>       }
>     }
>
> Note that when one is extending nominal types, we could simplify the
> syntax somewhat to make the same-type constraint implicit in the
> syntax:
>
>     extension<T> Array<T?> {
>       var someValues: [T] {
>         var result = [T]()
>         for opt in self {
>           if let value = opt { result.append(value) }
>         }
>        return result
>       }
>     }
>
> When we’re working with concrete types, we can use that syntax to
> improve the extension of concrete versions of generic types (per
> “Concrete same-type requirements”, above), e.g.,
>
>     extension Array<String> {
>       func makeSentence() -> String {
>         // uppercase first string, concatenate with spaces, add a period, whatever
>       }
>     }
>
> Minor extensions
>
> There are a number of minor extensions we can make to the generics
> system that don’t fundamentally change what one can express in Swift,
> but which can improve its expressivity.
>
> *Arbitrary requirements in protocols

I guess I don't see how this can be both something that doesn't
fundamentally change expressivity and something that will have a big
impact on the face of the standard library.

> Currently, a new protocol can inherit from other protocols, introduce
> new associated types, and add new conformance constraints to
> associated types (by redeclaring an associated type from an inherited
> protocol). However, one cannot express more general
> constraints. Building on the example from “Recursive protocol
> constraints”, we really want the element type of a Sequence’s
> SubSequence to be the same as the element type of the Sequence, e.g.,
>
>     protocol Sequence {
>       associatedtype Iterator : IteratorProtocol
>>       associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
>     }
>
> Hanging the where clause off the associated type is protocol not
> ideal, but that’s a discussion for another thread.
>
> *Typealiases in protocols and protocol extensions
>
> Now that associated types have their own keyword (thanks!), it’s
> reasonable to bring back “typealias”. Again with the Sequence
> protocol:
>
>     protocol Sequence {
>       associatedtype Iterator : IteratorProtocol
>       typealias Element = Iterator.Element   // rejoice! now we can refer to SomeSequence.Element rather than
>     SomeSequence.Iterator.Element
>     }

Is this something you expect to be customizable for a particular
conformance?  If not, I'm not sure it belongs in the protocol body (as
opposed to an extension)... or we need to draw a clearer distinction
between implementable requirements and things that are currently in
extensions.

> Default generic arguments 
>
> Generic parameters could be given the ability to provide default
> arguments, which would be used in cases where the type argument is not
> specified and type inference could not determine the type
> argument. For example:
>
>     public final class Promise<Value, Reason=Error> { … }
>    
>     func getRandomPromise() -> Promise<Int, ErrorProtocol> { … }
>    
>     var p1: Promise<Int> = …
>     var p2: Promise<Int, Error> = p1     // okay: p1 and p2 have the same type Promise<Int, Error>
>     var p3: Promise = getRandomPromise() // p3 has type Promise<Int, ErrorProtocol> due to type inference
>
> Generalized “class” constraints
>
> The “class” constraint can currently only be used for defining
> protocols. We could generalize it to associated type and type
> parameter declarations, e.g.,
>
>     protocol P {
>       associatedtype A : class
>     }
>    
>     func foo<T : class>(t: T) { }
>
> As part of this, the magical AnyObject protocol could be replaced with
> an existential with a class bound, so that it becomes a typealias:
>
>     typealias AnyObject = protocol<class>
>
> See the “Existentials” section, particularly “Generalized
> existentials”, for more information.

I don't see the “value” constraint mentioned.  I realize this has some
larger implications in the language but it seems like the obvious dual
of this one.

> *Allowing subclasses to override requirements satisfied by defaults
>
> When a superclass conforms to a protocol and has one of the protocol’s
> requirements satisfied by a member of a protocol extension, that
> member currently cannot be overridden by a subclass. For example:
>
>     protocol P {
>       func foo()
>     }
>    
>     extension P {
>       func foo() { print(“P”) }
>     }
>    
>     class C : P {
>       // gets the protocol extension’s 
>     }
>    
>     class D : C {
>       /*override not allowed!*/ func foo() { print(“D”) }
>     }
>    
>     let p: P = D()
>     p.foo() // gotcha: prints “P” rather than “D”!
>
> D.foo should be required to specify “override” and should be called
> dynamically.
>
> Major extensions to the generics model
>
> Unlike the minor extensions, major extensions to the generics model
> provide more expressivity in the Swift generics system and, generally,
> have a much more significant design and implementation cost.
>
> *Conditional conformances
>
> Conditional conformances express the notion that a generic type will
> conform to a particular protocol only under certain circumstances. For
> example, Array is Equatable only when its elements are Equatable:
>
>     extension Array : Equatable where Element : Equatable { }
>    
>     func ==<T : Equatable>(lhs: Array<T>, rhs: Array<T>) -> Bool { … }
>
> Conditional conformances are a potentially very powerful feature. One
> important aspect of this feature is how deal with or avoid overlapping
> conformances. For example, imagine an adaptor over a Sequence that has
> conditional conformances to Collection and MutableCollection:
>
>     struct SequenceAdaptor<S: Sequence> : Sequence { }
>     extension SequenceAdaptor : Collection where S: Collection { … }
>     extension SequenceAdaptor : MutableCollection where S: MutableCollection { }
>
> This should almost certainly be permitted, but we need to cope with or
> reject “overlapping” conformances:
>
>     extension SequenceAdaptor : Collection where S: SomeOtherProtocolSimilarToCollection { } // trouble: two ways

Missing generic parameter list here?

>     for SequenceAdaptor to conform to Collection
>
> See the section on “Private conformances” for more about the issues
> with having the same type conform to the same protocol multiple times.
>
> Variadic generics
>
> Currently, a generic parameter list contains a fixed number of generic
> parameters. If one has a type that could generalize to any number of
> generic parameters, the only real way to deal with it today involves
> creating a set of types. For example, consider the standard library’s
> “zip” function. It returns one of these when provided with two
> arguments to zip together:
>
>     public struct Zip2Sequence<Sequence1 : Sequence,
>                                Sequence2 : Sequence> : Sequence { … }
>    
>     public func zip<Sequence1 : Sequence, Sequence2 : Sequence>(
>                   sequence1: Sequence1, _ sequence2: Sequence2)
>                 -> Zip2Sequence<Sequence1, Sequence2> { … }
>
> Supporting three arguments would require copy-paste of those of those:
>
>     public struct Zip3Sequence<Sequence1 : Sequence,
>                                Sequence2 : Sequence,
>                                Sequence3 : Sequence> : Sequence { … }
>    
>     public func zip<Sequence1 : Sequence, Sequence2 : Sequence, Sequence3 : Sequence>(
>                   sequence1: Sequence1, _ sequence2: Sequence2, _ sequence3: sequence3)
>                 -> Zip3Sequence<Sequence1, Sequence2, Sequence3> { … }
>
> Variadic generics would allow us to abstract over a set of generic
> parameters. The syntax below is hopelessly influenced by C++11
> variadic templates (sorry), where putting an ellipsis (“…”) to the
> left of a declaration makes it a “parameter pack” containing zero or
> more parameters and putting an ellipsis to the right of a
> type/expression/ etc. expands the parameter packs within that
> type/expression into separate arguments. The important part is that we
> be able to meaningfully abstract over zero or more generic parameters,
> e.g.:
>
>     public struct ZipIterator<... Iterators : IteratorProtocol> : Iterator {  // zero or more type parameters, each
>     of which conforms to IteratorProtocol
>       public typealias Element = (Iterators.Element...)                       // a tuple containing the element
>     types of each iterator in Iterators
>    
>       var (...iterators): (Iterators...)    // zero or more stored properties, one for each type in Iterators 
>       var reachedEnd: Bool = false
>
>       public mutating func next() -> Element? {
>
>         if reachedEnd { return nil }
>
>         guard let values = (iterators.next()...) {   // call “next” on each of the iterators, put the results into a
>     tuple named “values"
>
>           reachedEnd = true
>
>           return nil
>
>         }
>
>         return values
>
>       }
>     }
>
>     public struct ZipSequence<...Sequences : Sequence> : Sequence {
>       public typealias Iterator = ZipIterator<Sequences.Iterator...>   // get the zip iterator with the iterator
>     types of our Sequences
>    
>       var (...sequences): (Sequences...)    // zero or more stored properties, one for each type in Sequences 
>    
>       // details ...
>     }
>
> Such a design could also work for function parameters, so we can pack
> together multiple function arguments with different types, e.g.,
>
>     public func zip<... Sequences : SequenceType>(... sequences: Sequences...) 
>                 -> ZipSequence<Sequences...> {
>       return ZipSequence(sequences...)
>     }
>
> Finally, this could tie into the discussions about a tuple “splat”
> operator. For example:
>
>     func apply<... Args, Result>(fn: (Args...) -> Result,    // function taking some number of arguments and
>     producing Result
>                                args: (Args...)) -> Result {  // tuple of arguments
>       return fn(args...)                                     // expand the arguments in the tuple “args” into
>     separate arguments
>     }
>
> Extensions of structural types
>
> Currently, only nominal types (classes, structs, enums, protocols) can
> be extended. One could imagine extending structural types—particularly
> tuple types—to allow them to, e.g., conform to protocols. For example,
> pulling together variadic generics, parameterized extensions, and
> conditional conformances, one could express “a tuple type is Equatable
> if all of its element types are Equatable”:
>
>     extension<...Elements : Equatable> (Elements...) : Equatable {   // extending the tuple type “(Elements…)” to be
>     Equatable
>     }
>
> There are some natural bounds here: one would need to have actual
> structural types. One would not be able to extend every type:
>
>     extension<T> T { // error: neither a structural nor a nominal type
>     }
>
> And before you think you’re cleverly making it possible to have a
> conditional conformance that makes every type T that conforms to
> protocol P also conform to protocol Q, see the section "Conditional
> conformances via protocol extensions”, below:
>
>     extension<T : P> T : Q { // error: neither a structural nor a nominal type
>     }
>
> Syntactic improvements
>
> There are a number of potential improvements we could make to the
> generics syntax. Such a list could go on for a very long time, so I’ll
> only highlight some obvious ones that have been discussed by the Swift
> developers.
>
> *Default implementations in protocols
>
> Currently, protocol members can never have implementations. We could
> allow one to provide such implementations to be used as the default if
> a conforming type does not supply an implementation, e.g.,
>
>     protocol Bag {
>       associatedtype Element : Equatable
>       func contains(element: Element) -> Bool
>    
>       func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool {
>         for x in elements {
>           if contains(x) { return true }
>         }
>         return false
>       }
>     }
>    
>     struct IntBag : Bag {
>       typealias Element = Int
>       func contains(element: Int) -> Bool { ... }
>    
>       // okay: containsAll requirement is satisfied by Bag’s default implementation
>     }
>
> One can get this effect with protocol extensions today, hence the
> classification of this feature as a (mostly) syntactic improvement:
>
>     protocol Bag {
>       associatedtype Element : Equatable
>       func contains(element: Element) -> Bool
>    
>       func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool
>     }
>    
>     extension Bag {
>       func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool {
>         for x in elements {
>           if contains(x) { return true }
>         }
>         return false
>       }
>     }
>
> *Moving the where clause outside of the angle brackets
>
> The “where” clause of generic functions comes very early in the
> declaration, although it is generally of much less concern to the
> client than the function parameters and result type that follow
> it. This is one of the things that contributes to “angle bracket
> blindness”. For example, consider the containsAll signature above:
>
>     func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool
>
> One could move the “where” clause to the end of the signature, so that
> the most important parts—name, generic parameter, parameters, result
> type—precede it:
>
>     func containsAll<S: Sequence>(elements: S) -> Bool 
>
>            where Sequence.Iterator.Element == Element
>
> *Renaming “protocol<…>” to “Any<…>”.
>
> The “protocol<…>” syntax is a bit of an oddity in Swift. It is used to
> compose protocols together, mostly to create values of existential
> type, e.g.,
>
>     var x: protocol<NSCoding, NSCopying>
>
> It’s weird that it’s a type name that starts with a lowercase letter,
> and most Swift developers probably never deal with this feature unless
> they happen to look at the definition of Any:
>
>     typealias Any = protocol<>
>
> “Any” might be a better name for this functionality. “Any” without
> brackets could be a keyword for “any type”, and “Any” followed by
> brackets could take the role of “protocol<>” today:
>
>     var x: Any<NSCoding, NSCopying>
>
> That reads much better: “Any type that conforms to NSCoding and
> NSCopying”. See the section "Generalized existentials” for additional
> features in this space.
>
> Maybe
>
> There are a number of features that get discussed from time-to-time,
> while they could fit into Swift’s generics system, it’s not clear that
> they belong in Swift at all. The important question for any feature in
> this category is not “can it be done” or “are there cool things we can
> express”, but “how can everyday Swift developers benefit from the
> addition of such a feature?”. Without strong motivating examples, none
> of these “maybes” will move further along.
>
> Dynamic dispatch for members of protocol extensions
>
> Only the requirements of protocols currently use dynamic dispatch,
> which can lead to surprises:
>
>     protocol P {
>       func foo()
>     }
>    
>     extension P {
>       func foo() { print(“P.foo()”)
>       func bar() { print(“P.bar()”)
>     }
>    
>     struct X : P {
>       func foo() { print(“X.foo()”)
>       func bar() { print(“X.bar()”)
>     }
>    
>     let x = X()
>     x.foo() // X.foo()
>     x.bar() // X.bar()
>    
>     let p: P = X()
>     p.foo() // X.foo()
>     p.bar() // P.bar()
>
> Swift could adopt a model where members of protocol extensions are
> dynamically dispatched.
>
> Named generic parameters
>
> When specifying generic arguments for a generic type, the arguments
> are always positional: Dictionary<String, Int> is a Dictionary whose
> Key type is String and whose Value type is Int, by convention. One
> could permit the arguments to be labeled, e.g.,
>
>     var d: Dictionary<Key: String, Value: Int>
>
> Such a feature makes more sense if Swift gains default generic
> arguments, because generic argument labels would allow one to skip
> defaulted arguments.
>
> Generic value parameters
>
> Currently, Swift’s generic parameters are always types. One could
> imagine allowing generic parameters that are values, e.g.,
>
>     struct MultiArray<T, let Dimensions: Int> { // specify the number of dimensions to the array
>       subscript (indices: Int...) -> T {
>         get {
>           require(indices.count == Dimensions)
>           // ...
>         }
>     }
>
> A suitably general feature might allow us to express fixed-length
> array or vector types as a standard library component, and perhaps
> also allow one to implement a useful dimensional analysis
> library. Tackling this feature potentially means determining what it
> is for an expression to be a “constant expression” and diving into
> dependent-typing, hence the “maybe”.
>
> Higher-kinded types
>
> Higher-kinded types allow one to express the relationship between two
> different specializations of the same nominal type within a
> protocol. For example, if we think of the Self type in a protocol as
> really being “Self<T>”, it allows us to talk about the relationship
> between “Self<T>” and “Self<U>” for some other type U. For example, it
> could allow the “map” operation on a collection to return a collection
> of the same kind but with a different operation, e.g.,
>
>     let intArray: Array<Int> = …
>     intArray.map { String($0) } // produces Array<String>
>     let intSet: Set<Int> = …
>     intSet.map { String($0) }   // produces Set<String>
>
> Potential syntax borrowed from one thread on higher-kinded types uses
> ~= as a “similarity” constraint to describe a Functor protocol:
>
>     protocol Functor {
>       associatedtype A
>       func fmap<FB where FB ~= Self>(f: A -> FB.A) -> FB
>     }
>
> Specifying type arguments for uses of generic functions
>
> The type arguments of a generic function are always determined via
> type inference. For example, given:
>
>     func f<T>(t: T)
>
> one cannot directly specify T: either one calls “f” (and T is
> determined via the argument’s type) or one uses “f” in a context where
> it is given a particular function type (e.g., “let x: (Int) -> Void =
> f” would infer T = Int). We could permit explicit specialization here,
> e.g.,
>
>     let x = f<Int> // x has type (Int) -> Void
>
> Unlikely
>
> Features in this category have been requested at various times, but
> they don’t fit well with Swift’s generics system because they cause
> some part of the model to become overly complicated, have unacceptable
> implementation limitations, or overlap significantly with existing
> features.
>
> Generic protocols
>
> One of the most commonly requested features is the ability to
> parameterize protocols themselves. For example, a protocol that
> indicates that the Self type can be constructed from some specified
> type T:
>
>     protocol ConstructibleFromValue<T> {
>       init(_ value: T)
>     }
>
> Implicit in this feature is the ability for a given type to conform to
> the protocol in two different ways. A “Real” type might be
> constructible from both Float and Double, e.g.,
>
>     struct Real { … }
>     extension Real : ConstructibleFrom<Float> {
>       init(_ value: Float) { … }
>     }
>     extension Real : ConstructibleFrom<Double> {
>       init(_ value: Double) { … }
>     }
>
> Most of the requests for this feature actually want a different
> feature. They tend to use a parameterized Sequence as an example,
> e.g.,
>
>     protocol Sequence<Element> { … }
>    
>     func foo(strings: Sequence<String>) {  /// works on any sequence containing Strings
>       // ...
>     }
>
> The actual requested feature here is the ability to say “Any type that
> conforms to Sequence whose Element type is String”, which is covered
> by the section on “Generalized existentials”, below.
>
> More importantly, modeling Sequence with generic parameters rather
> than associated types is tantalizing but wrong: you don’t want a type
> conforming to Sequence in multiple ways, or (among other things) your
> for..in loops stop working, and you lose the ability to dynamically
> cast down to an existential “Sequence” without binding the Element
> type (again, see “Generalized existentials”). Use cases similar to the
> ConstructibleFromValue protocol above seem too few to justify the
> potential for confusion between associated types and generic
> parameters of protocols; we’re better off not having the latter.
>
> Private conformances 
>
> Right now, a protocol conformance can be no less visible than the
> minimum of the conforming type’s access and the protocol’s
> access. Therefore, a public type conforming to a public protocol must
> provide the conformance publicly.  One could imagine removing that
> restriction, so that one could introduce a private conformance:
>
>     public protocol P { }
>     public struct X { }
>     extension X : internal P { … } // X conforms to P, but only within this module
>
> The main problem with private conformances is the interaction with dynamic casting. If I have this code:
>
>     func foo(value: Any) {
>       if let x = value as? P { print(“P”) }
>     }
>    
>     foo(X())
>
> Under what circumstances should it print “P”? If foo() is defined
> within the same module as the conformance of X to P? If the call is
> defined within the same module as the conformance of X to P? Never?
> Either of the first two answers requires significant complications in
> the dynamic casting infrastructure to take into account the module in
> which a particular dynamic cast occurred (the first option) or where
> an existential was formed (the second option), while the third answer
> breaks the link between the static and dynamic type systems—none of
> which is an acceptable result.
>
> Conditional conformances via protocol extensions
>
> We often get requests to make a protocol conform to another
> protocol. This is, effectively, the expansion of the notion of
> “Conditional conformances” to protocol extensions. For example:
>
>     protocol P {
>       func foo()
>     }
>    
>     protocol Q {
>       func bar()
>     }
>    
>     extension Q : P { // every type that conforms to Q also conforms to P
>       func foo() {    // implement “foo” requirement in terms of “bar"
>         bar()
>       }
>     }
>    
>     func f<T: P>(t: T) { … }
>    
>     struct X : Q {
>       func bar() { … }
>     }
>    
>     f(X()) // okay: X conforms to P through the conformance of Q to P
>
> This is an extremely powerful feature: is allows one to map the
> abstractions of one domain into another domain (e.g., every Matrix is
> a Graph). However, similar to private conformances, it puts a major
> burden on the dynamic-casting runtime to chase down arbitrarily long
> and potentially cyclic chains of conformances, which makes efficient
> implementation nearly impossible.
>
> Potential removals
>
> The generics system doesn’t seem like a good candidate for a reduction
> in scope; most of its features do get used fairly pervasively in the
> standard library, and few feel overly anachronistic. However...
>
> Associated type inference
>
> Associated type inference is the process by which we infer the type
> bindings for associated types from other requirements. For example:
>
>     protocol IteratorProtocol {
>       associatedtype Element
>       mutating func next() -> Element?
>     }
>    
>     struct IntIterator : IteratorProtocol {
>       mutating func next() -> Int? { … }  // use this to infer Element = Int
>     }
>
> Associated type inference is a useful feature. It’s used throughout
> the standard library, and it helps keep associated types less visible
> to types that simply want to conform to a protocol. On the other hand,
> associated type inference is the only place in Swift where we have a
> global type inference problem: it has historically been a major source
> of bugs, and implementing it fully and correctly requires a
> drastically different architecture to the type checker. Is the value
> of this feature worth keeping global type inference in the Swift
> language, when we have deliberatively avoided global type inference
> elsewhere in the language?
>
> Existentials
>
> Existentials aren’t really generics per se, but the two systems are
> closely intertwined due to their mutable dependence on protocols.
>
> *Generalized existentials
>
> The restrictions on existential types came from an implementation
> limitation, but it is reasonable to allow a value of protocol type
> even when the protocol has Self constraints or associated types. For
> example, consider IteratorProtocol again and how it could be used as
> an existential:
>
>     protocol IteratorProtocol {
>       associatedtype Element
>       mutating func next() -> Element?
>     }
>    
>     let it: IteratorProtocol = …
>     it.next()   // if this is permitted, it could return an “Any?”, i.e., the existential that wraps the actual
>     element
>
> Additionally, it is reasonable to want to constrain the associated
> types of an existential, e.g., “a Sequence whose element type is
> String” could be expressed by putting a where clause into
> “protocol<…>” or “Any<…>” (per “Renaming protocol<…> to Any<…>”):
>
>     let strings: Any<Sequence where .Iterator.Element == String> = [“a”, “b”, “c”]
>
> The leading “.” indicates that we’re talking about the dynamic type,
> i.e., the “Self” type that’s conforming to the Sequence
> protocol. There’s no reason why we cannot support arbitrary “where”
> clauses within the “Any<…>”. This very-general syntax is a bit
> unwieldy, but common cases can easily be wrapped up in a generic
> typealias (see the section “Generic typealiases” above):
>
>     typealias AnySequence<Element> = Any<Sequence where .Iterator.Element == Element>
>     let strings: AnySequence<String> = [“a”, “b”, “c”]
>
> Opening existentials
>
> Generalized existentials as described above will still have trouble
> with protocol requirements that involve Self or associated types in
> function parameters. For example, let’s try to use Equatable as an
> existential:
>
>     protocol Equatable {
>       func ==(lhs: Self, rhs: Self) -> Bool
>       func !=(lhs: Self, rhs: Self) -> Bool
>     }
>    
>     let e1: Equatable = …
>     let e2: Equatable = …
>     if e1 == e2 { … } // error: e1 and e2 don’t necessarily have the same dynamic type
>
> One explicit way to allow such operations in a type-safe manner is to
> introduce an “open existential” operation of some sort, which extracts
> and gives a name to the dynamic type stored inside an existential. For
> example:
>
>     if let storedInE1 = e1 openas T {     // T is a the type of storedInE1, a copy of the value stored in e1
>
>       if let storedInE2 = e2 as? T {      // is e2 also a T?
>
>         if storedInE1 == storedInE2 { … } // okay: storedInT1 and storedInE2 are both of type T, which we know is
>     Equatable
>
>       }
>
>     }
>
> Thoughts?

This is certainly... a lot.  I've wanted so much from the generics
system over the years that it's hard to remember whether everything
important is in this list.  I'll have to give that some more thought.

-- 
-Dave



More information about the swift-evolution mailing list