Sent from my iPhone
> On May 2, 2016, at 3:58 PM, David Hart <da...@hartbit.com> wrote: > > I’d like to continue moving Completing Generics forward for Swift 3 with > proposals. Can Douglas, or someone from the core team, tell me if the topics > mentioned in Removing unnecessary restrictions require proposals or if bug > reports should be opened for them instead? I'd classify everything in that section as a bug, so long as we're restricting ourselves to the syntax already present in the language. Syntactic improvements (e.g., for same-type-to-concrete constraints) would require a proposal. - Doug > >> On 03 Mar 2016, at 02:22, Douglas Gregor via swift-evolution >> <swift-evolution@swift.org> wrote: >> >> Hi all, >> >> Introduction >> >> The “Complete Generics” goal for Swift 3 has been fairly ill-defined thus >> fair, with just this short blurb in the list of goals: >> >> Complete generics: Generics are used pervasively in a number of Swift >> libraries, especially the standard library. However, there are a number of >> generics features the standard library requires to fully realize its vision, >> including recursive protocol constraints, the ability to make a constrained >> extension conform to a new protocol (i.e., an array of Equatable elements is >> Equatable), and so on. Swift 3.0 should provide those generics features >> needed by the standard library, because they affect the standard library's >> ABI. >> 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. >> 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 >> >> 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 >> >> 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 >> } >> >> >> 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. >> >> >> *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 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? >> >> - Doug >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org >> https://lists.swift.org/mailman/listinfo/swift-evolution >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution