[swift-evolution] Enhanced existential types proposal discussion

Austin Zheng austinzheng at gmail.com
Tue May 17 23:52:16 CDT 2016


On Tue, May 17, 2016 at 1:25 PM, Matthew Johnson <matthew at anandabits.com>
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
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160517/6c4a9813/attachment.html>


More information about the swift-evolution mailing list