I created two bug requests for Recursive protocol constraints and Nested generics and will write a proposal for Concrete same-type requirements.
[SR-1445] Recursive protocol constraints <https://bugs.swift.org/browse/SR-1445> [SR-1446] Nested generics <https://bugs.swift.org/browse/SR-1446> > On 03 May 2016, at 09:58, Douglas Gregor <dgre...@apple.com> wrote: > > > > Sent from my iPhone > > On May 2, 2016, at 3:58 PM, David Hart <da...@hartbit.com > <mailto: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 <mailto: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 >>> <https://github.com/apple/swift/blob/master/docs/Generics.rst>, 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 >>> <http://www.urbandictionary.com/define.php?term=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 <http://www.jot.fm/issues/issue_2008_02/article2/> (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 >>> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/002736.html> >>> 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 <mailto:swift-evolution@swift.org> >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution