an alternative to the Any<> syntax with tentative grammar and some examples (still a WIP)
https://gist.github.com/lmihalkovic/8aa66542f5cc4592e967bade260477ef > On Jun 6, 2016, at 1:20 AM, Douglas Gregor via swift-evolution > <[email protected]> wrote: > > >> On May 18, 2016, at 12:35 AM, Austin Zheng <[email protected] >> <mailto:[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 >> >> <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…” > >> >> Austin >> >> On Tue, May 17, 2016 at 9:52 PM, Austin Zheng <[email protected] >> <mailto:[email protected]>> wrote: >> >> >> On Tue, May 17, 2016 at 1:25 PM, Matthew Johnson <[email protected] >> <mailto:[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
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
