Sent from my iPhone
> On Jun 5, 2016, at 6:41 PM, Matthew Johnson <[email protected]> wrote: > > > > Sent from my iPad > >> On Jun 5, 2016, at 6:20 PM, Douglas Gregor <[email protected]> wrote: >> >> >>> On May 18, 2016, at 12:35 AM, Austin Zheng <[email protected]> wrote: >>> >>> I've put together a considerably more detailed draft proposal, taking into >>> account as much of Matthew's feedback as I could. You can find it below: >>> >>> https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md >>> >>> Since there is no chance this will come up for review anytime soon, I >>> expect to make significant revisions to it over the next month or so. Any >>> feedback would be greatly appreciated. >> >> This is very much Swift 4 territory, but I can’t help myself… so… >> >> The actual feature description is spread out through this very long >> document, with user-facing ideas (e.g., using “anonymous associated types”) >> intermixed with deeper technical details (existential type equivalence), so >> it’s very daunting to read. Please bring the user-facing features to the >> front (“Proposed Solution”) with examples, and save the deeper technical >> details for “Detailed Design”. You want more readers to make it through the >> part that affects them. >> >> Shortcut 'dot' notation: If there is only one protocol with associated types >> specified in the requirements, and there are no nested Any<...> requirements >> with where clauses of their own, that protocol's name can be omitted from >> the whereclause constraints: >> >> // Okay >> // Would otherwise be Any< ~ where Collection.Element == Int> >> let a : Any<class, Collection, Any<Streamable, CustomStringConvertible> >> where .Element == Int> >> >> // NOT ALLOWED >> // Both Collection and OptionSetType have associated types. >> let b : Any<Collection, OptionSetType where .Element == Int> >> FWIW, I think “.Element == Int” should be the only syntax. In generic >> signatures, if you have two different protocols with same-named associated >> types, and a given type parameter (or associated type) conforms to both >> protocols, the associated types are (implicitly) made equivalent via an >> inferred same-type constraint. So there’s no reason to introduce the >> “Collection.Element == Int” syntax, because the “Collection” part is >> basically irrelevant. >> >> Once existentials have been suitably enhanced, there is a strong analogy >> between an existential and a generic signature with a single type parameter >> that you can’t name. An existential Any<Collection where .Element : >> Equatable> has most of the same characteristics as a generic something with >> the signature <T : Collection where T.Element : Equatable>. Specifically, >> the sections on “Existential type equivalence”, “Ordering”, “Real types to >> anonymous associated types”, “Anonymous associated types to real types”. >> could be reduced to a few small, simple examples and a mention of the >> analogous behavior of generics. It will be far easier to explain this way, >> and readers don’t need to get immersed in the details. Where there are >> differences vs. generics, that’s important to point out. >> >> “Associated typealias rewriting”: this also falls out of the equivalence >> with generics + SE-0092. >> >> “Associated types and member exposure”: you don’t make the point that it >> only makes sense to refer to the associated types of a let constant; a var >> could change its type dynamically, which would invalidate the typing rules. >> Did you consider just using “x.dynamicType” in the type grammar for this? >> It’s more general, in that you can refer to associated types but also talk >> about the dynamic type of “x” itself, e.g., >> >> let x: Equatable = … >> let y: Equatable = … >> if let yAsX = y as? x.dynamicType { … x == yAsX … } >> >> which is (almost?) as powerful as a general “open” expression. >> >> I’m not a fan of the “anonymous associated types” terminology: these are >> associated types of a type of some runtime-defined value. The only thing >> “anonymous” about them is that it’s harder to spell the base type; >> otherwise, they’re just like associated types of a generic type parameter. >> Again, the generics analogy is strong here. >> >> FWIW, I don’t think we’ll ever need “opening existentials” with what you’ve >> described here. Also, remember that a method of a protocol extension >> essentially opens “Self”, so we already have one way to open an existential >> (and that’s probably enough). >> >> I was a little surprised you didn’t point out that AnyObject could become >> >> typealias AnyObject = Any<class> >> >> or give the nice “AnyCollection” syntax: >> >> typealias AnyCollection<T> = Any<Collection where .Element == T> >> >> the latter of which is fairly important, because it gives nice syntactic >> sure to one of the most highly-requested features [*]. I’d suggest having >> that example very, very early. >> >> - Doug >> >> [*] That generally comes in as “Swift should have parameterized protocols…” > > Great feedback here Doug. > > FWIW, we also occasionally get "Swift should have parameterized protocols" in > the context of multiple conformances by the same concrete type (as in things > like ConvertibleTo<T> protocol). I know. From the bugs I've seen it's at least 10x as many requests for "any collection of some element type" as for any actually reason why one would need parameterize a protocols. - Doug > > >> >>> >>> Austin >>> >>>> On Tue, May 17, 2016 at 9:52 PM, Austin Zheng <[email protected]> >>>> wrote: >>>> >>>> >>>>> On Tue, May 17, 2016 at 1:25 PM, Matthew Johnson <[email protected]> >>>>> wrote: >>>>>>> >>>>>>> >>>>>>>> >>>>>>>> Within the angle brackets are zero or more 'clauses'. Clauses are >>>>>>>> separated by semicolons. (This is so commas can be used in where >>>>>>>> constraints, below. Better ideas are welcome. Maybe it's not >>>>>>>> necessary; we can use commas exclusively.) >>>>>>> >>>>>>> I’m not a fan of the semicolon idea. I don’t see any reason for this. >>>>>>> The `where` keyword separates the protocol list from the constraints >>>>>>> just fine. The list on either side should be able to use commas with >>>>>>> no problem (or line breaks if that proposal goes through). >>>>>> >>>>>> I'm leaning towards getting rid of the commas, but would like to write >>>>>> out a few 'dummy' examples to see if there are any readability issues >>>>>> that arise. >>>>> >>>>> Replaced with what? Whitespace separation? I suppose that might work >>>>> for the protocol list but it feels inconsistent with the rest of Swift. >>>>> Commas plus (hopefully) the alternative of newline seem like the right >>>>> direction to me. >>>> >>>> Sorry, I completely misspoke (mistyped?). I meant I want to get rid of the >>>> semicolons and use commas. I've come to the conclusion that there are no >>>> readability issues, protocol<> already uses commas, and semicolons used in >>>> this manner don't have a precedent anywhere else in the language. >>>> >>>>> >>>>>>>> >>>>>>>> There are five different possible clauses: >>>>>>>> >>>>>>>> 'class'. Must be the first clause, if present. Places a constraint on >>>>>>>> the existential to be any class type. (Implies: Only one can exist. >>>>>>>> Mutually exclusive with class name clause.) >>>>>>>> >>>>>>>> (In the future a follow-up proposal should add in 'struct' or 'value' >>>>>>>> as a counterpart.) >>>>>>> >>>>>>> If we’re going to allow `struct` we should also allow `enum`. `value` >>>>>>> would allow either of those. >>>>>> >>>>>> Of course. A future proposal can allow list members to discuss the exact >>>>>> details as to how struct, value, or enum specifiers should work. >>>>> >>>>> Yep, agree. Just mentioning that if we’re going to reference it we >>>>> should not leave obvious holes in what would be considered. :) >>>> >>>> Absolutely. >>>> >>>>> >>>>>>>> >>>>>>>> Class name. Must be the first clause, if present. (Implies: Only one >>>>>>>> can exist. Mutually exclusive with 'class'.) Places a constraint on >>>>>>>> the existential (not really an existential anymore) to be an instance >>>>>>>> of the class, or one of its subclasses. >>>>>>> >>>>>>> It is still be an existential if it includes protocol requirements that >>>>>>> the class does not fulfill. For example, you might have Any<UIView, >>>>>>> SomeProtocol> where UIView does not conform to SomeProtocol, but >>>>>>> various subclasses do. >>>>>> >>>>>> Fair enough. (I don't think the way things work would be affected.) >>>>>> >>>>>>> Your proposal doesn’t discuss composing Any in the way that Adrian’s >>>>>>> did like this: >>>>>>> >>>>>>> typealias Foo = Any<SomeClass, SomeProtocol, OtherProtocol> >>>>>>> Any<AnotherProtocol, Foo> >>>>>> >>>>>> I didn't think it needed to be discussed. An Any<...> existential type >>>>>> is a type 'expression' just like any other, and should be allowed to >>>>>> participate in other Any<...>s. >>>>>> >>>>>>> >>>>>>> I like the idea of composition as it allows us to factor out >>>>>>> constraints. If we are going to do that we should allow a class to be >>>>>>> specified in the composition as long is it is a subclass of all class >>>>>>> requirements of Any types it composes. For example, this should be >>>>>>> allowed: >>>>>>> >>>>>>> typealias Bar = Any<SubclassOfSomeClass, Foo, AnotherProtocol> >>>>>>> >>>>>>> This is still one class requirement for Bar, it just refines the class >>>>>>> requirement of Foo to be SubclassOfSomeClass rather than just SomeClass. >>>>>> >>>>>> This is a good point. There should be clarification as to how special >>>>>> cases of Any<...> used in another Any<...> behave. For example, like you >>>>>> said Any<MyClass, Any<SomeSubclassOfMyClass, Protocol>> should be valid. >>>>>> This will go into any proposal that emerges from the discussion. >>>>> >>>>> Yes, this is why we need to discuss Any composition. There are also >>>>> cases of incompatible associated type constraints which need to be >>>>> rejected (such as composing two Any’s where one has Element == String and >>>>> another has Element == Int). >>>>> >>>>>> >>>>>>> >>>>>>>> Example: Any<UIViewController; UITableViewDataSource; >>>>>>>> UITableViewDelegate> >>>>>>>> "Any UIViewController or subclass which also satisfies the table view >>>>>>>> data source and delegate protocols" >>>>>>>> Dynamic protocol. This is entirely composed of the name of a protocol >>>>>>>> which has no associated types or Self requirement. >>>>>>>> Example: Any<CustomStringConvertible; BooleanType> >>>>>>>> "Any type which conforms to both the CustomStringConvertible and >>>>>>>> BooleanType protocols" >>>>>>>> >>>>>>>> I'm going to use 'static protocol' to refer to a protocol with >>>>>>>> associated types or self requirements. Feel free to propose a more >>>>>>>> sound name. >>>>>>>> >>>>>>>> Self-contained static protocol, simple. This is composed of the name >>>>>>>> of a static protocol, optionally followed by a 'where' clause in which >>>>>>>> the associated types can be constrained (with any of the three basic >>>>>>>> conformance types: subclassing, protocol conformance, or type >>>>>>>> equality). Associated types are referred to with a leading dot. >>>>>>> >>>>>>> Please do not introduce terms “dynamic protocol” and “static protocol”. >>>>>>> We want to support existentials of protocols that have self or >>>>>>> associated type requirements. The dynamic vs static distinction is a >>>>>>> limitation of the current implementation of Swift and doesn’t make >>>>>>> sense for the long term vision. >>>>>> >>>>>> I'm not trying to introduce new terms, these are just placeholders. At >>>>>> the same time "protocols with self or associated type requirements" is >>>>>> cumbersome to work with and it would be nice for someone to come up with >>>>>> a descriptive term of art for referring to them. >>>>> >>>>> I agree that a better term would be useful. In the meantime, I would >>>>> prefer something like “trivial” and “nontrivial” protocols. >>>> >>>> I've decided to just use the full name until the community comes up with >>>> better names. Clarity is preferable to brevity in this case. >>>> >>>>> >>>>>> >>>>>>> >>>>>>>> >>>>>>>> Example: Any<Collection where .Generator.Element : NSObject, >>>>>>>> .Generator.Element : SomeProtocol> >>>>>>>> "Any type that is a Collection, whose elements are NSObjects or their >>>>>>>> subclasses conforming to SomeProtocol.” >>>>>>> >>>>>>> Swift does not allow disjunction of requirements. Only conjunctions >>>>>>> are supported. That means the correct reading is: >>>>>>> >>>>>>> "Any type that is a Collection, whose elements are NSObjects and their >>>>>>> subclasses conforming to SomeProtocol.” >>>>>> >>>>>> Yes, that is what I meant. "whose elements are (NSObjects or their >>>>>> subclasses) conforming to SomeProtocol”. >>>>> >>>>> Ok, good. Wasn’t quite clear to me. >>>> >>>> Yes, the verbiage will need to be clearer in the future. That sentence >>>> could be ambiguously parsed. >>>> >>>>> >>>>>>>> >>>>>>>> Bound static protocol. This is the same as a self-contained static >>>>>>>> protocol, but with a leading "<name> as " which binds the protocol to >>>>>>>> a generic typealias. The name can be then be used in subsequent >>>>>>>> clauses to build constraints. >>>>>>>> >>>>>>>> Example: Any<T as Collection; IntegerLiteralConvertible where >>>>>>>> .IntegerLiteralType == T.Element>. >>>>>>>> "Any type that is a Collection, and also can be built from an integer >>>>>>>> literal, in which the collection elements are the same type as the >>>>>>>> type of the integer used for the integer literal conformance.” >>>>>>> >>>>>>> I’m not sure about this, but if we’re going to do it it should be the >>>>>>> other way around: `Collection as T` with the alias after the name of >>>>>>> the protocol. >>>>>> >>>>>> I like this, it flows better. "Protocol as T where Protocol.Foo == Int, >>>>>> Protocol.Bar : Baz”. >>>>> >>>>> Why did you introduce an alias here and then not use it? Did you mean >>>>> "Protocol as T where T.Foo == Int, T.Bar : Baz" >>>> >>>> Another result of rushing to compose an email. Sorry! >>>> >>>>> >>>>>> >>>>>>> You are also using “dot shorthand” here to refer to an associated type >>>>>>> of IntegerLiteralConvertible. I think “dot shorthand” should be >>>>>>> limited to cases where there is only one protocol that is getting >>>>>>> constrained. In other cases, we need to be clear about which protocol >>>>>>> we are referring to. >>>>>> >>>>>> I borrowed dot shorthand from the generics manifesto. But you are right, >>>>>> it should only be allowed if there is one protocol with associated types >>>>>> or self requirements clause in the Any<...> construction. >>>>> >>>>> I would actually go further and limit it to one protocol period, and >>>>> possibly even to one protocol and no type names (as types can have nested >>>>> types and typealiases). When we allow shorthand it should be immediately >>>>> unambiguous what the shorthand references with no need to look at type or >>>>> protocol declarations. >>>> >>>> It might be desirable to propose the proposal with no allowance for >>>> shorthand, and have the dot shorthand be a smaller follow-up proposal. >>>> >>>>> >>>>>> >>>>>>> >>>>>>>> >>>>>>>> There will be rules to prevent recursive nesting. For example, if >>>>>>>> generic typealiases are allowed, they cannot refer to each other in a >>>>>>>> circular manner (like how structs can't contain themeselves, and you >>>>>>>> can't create a cyclic graph of enums containing themselves). >>>>>>>> >>>>>>>> How an existential can be used depends on what guarantees are provided >>>>>>>> by the clauses. For example, 'Any<Equatable>' can't be used for much; >>>>>>>> if there were any methods on Equatable that did not use the associated >>>>>>>> types at all you'd be able to call them, but that's about it. However, >>>>>>>> 'Any<Equatable where .Self == String>' would allow for == to be called >>>>>>>> on instances. (This is a stupid example, since Any<Equatable where >>>>>>>> .Self == String> is equivalent to 'String', but there are almost >>>>>>>> certainly useful examples one could come up with.) >>>>>>>> >>>>>>>> In order of increasing 'power': >>>>>>>> Don't constrain any associated types. You can pass around >>>>>>>> Any<Equatable>s, but that's about it. >>>>>>>> Constrain associated types to conform to protocols. >>>>>>>> Fully constrain associated types. >>>>>>> >>>>>>> I think we need to spell out pretty clearly what members we expect to >>>>>>> be available or not available. This section probably needs the most >>>>>>> design and elaboration. >>>>>>> >>>>>>> For example, we probably can’t access a member who uses an associated >>>>>>> type as an input unless it is constrained to a specific type. On the >>>>>>> other hand output types probably don’t need to limit access to a >>>>>>> member. However, if the output type is Self or an associated type the >>>>>>> visible signature would have an output type which has the relevant >>>>>>> constraints of the existential applied, but no more. In some cases >>>>>>> this means the output type would simply be Any. >>>>>> >>>>>> Absolutely. This is vaguely what I had in mind but I wanted to get >>>>>> something down first. Thanks for thinking through some of the >>>>>> implications :). >>>>> >>>>> That’s what I thought. Just wanted to start the process of elaborating >>>>> expectations. >>>>> >>>>>> >>>>>>> >>>>>>> Where this really gets tricky is for compound types like functions, >>>>>>> generic types, etc. Working out the details in these cases is pretty >>>>>>> complex. I will defer to Doug on whether it is best to just defer >>>>>>> those cases to the future, leave them up to the implementer, or try to >>>>>>> work out all of the relevant details in the proposal (in which case we >>>>>>> probably need a type system expert to help!). >>>>>> >>>>>> Yes, exactly! For example, can Any<...> existentials involving protocols >>>>>> with associated types or self requirements be used within generic >>>>>> function or type definitions? Maybe there's an argument that existential >>>>>> types of this nature are redundant if you have access to generics (e.g. >>>>>> defining a property on a generic type that is a Collection containing >>>>>> Ints; you should be able to do that today). On the other hand, maybe >>>>>> there are use cases I haven't thought of… >>>>> >>>>> I see no reason they shouldn’t be. They are not redundant at all. For >>>>> example, you may want to store instances in a heterogeneous collection. >>>>> You need existentials to do that. >>>>> >>>>> A simple example of what I was referring to there is something like this: >>>>> >>>>> protocol P { >>>>> associatedtype Foo >>>>> >>>>> func bar(callback: (Foo) -> ()) >>>>> } >>>>> >>>>> In other words, types in the signature of a protocol member are complex >>>>> types that reference Self or associated types. I think you really need a >>>>> formal understanding of the type system to understand how to expose these >>>>> members through a constrained existential. We can probably understand >>>>> the expected behavior in some of the simpler cases on a case by case >>>>> basis, but that approach doesn’t scale at all and is arbitrary. If >>>>> they’re going to be supported an expert is going to need to be involved >>>>> in the design. >>>> >>>> Yes. I have some ideas regarding this topic. >>>> >>>>> >>>>>> >>>>>>> >>>>>>> One area you didn’t touch on is “opening” the existential? Is that out >>>>>>> of scope for this proposal? That would be fine with me as this >>>>>>> proposal is already taking on a lot. But if so, you should mention >>>>>>> something about future directions as it is pretty closely related to >>>>>>> this proposal. >>>>>> >>>>>> Yes, existential opening is explicitly separate from this (although I >>>>>> wanted to mention it in the section where I talk about how >>>>>> Any<Equatable> is not very useful). But you are absolutely right, this >>>>>> proposal should discuss how it wants to interact with possible future >>>>>> directions. >>>>>> >>>>>>> >>>>>>> Another area you didn’t touch on is whether Any constructs (and >>>>>>> typealiases referring to them) should be usable as generic constraints. >>>>>>> I would expect this to be possible but I think we need to spell it out. >>>>>> >>>>>> I'm hoping for community input. This is a tricky subject, and at some >>>>>> point we'll bump into implementation limitations. >>>>> >>>>> I don’t think it’s too tricky. You can just unpack the constraints of >>>>> the Any into the list of generic constraints. Maybe I’m missing >>>>> something, but I don’t think so. >>>>> >>>>>> >>>>>>> >>>>>>> -Matthew >>
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
