Enhanced existential types proposal discussion


(Austin Zheng) #1

I put together the skeleton of a proposal detailing enhancements to how
associated types can be referenced in Swift 3+. It's certainly not ready
for submission, but I think it gets across my ideas pretty well. Would love
to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how
Swift developers program for years, so it needs to be done right.

See below:

*Proposal*

An existential type is defined using the "Any<...>" construct. Existential
types can be nested.

The empty existential type "Any<>" is typealiased to 'Any'.

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.)

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.)

   - 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.

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.

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."

   - 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."

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.


(Matthew Johnson) #2

I put together the skeleton of a proposal detailing enhancements to how associated types can be referenced in Swift 3+. It's certainly not ready for submission, but I think it gets across my ideas pretty well. Would love to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how Swift developers program for years, so it needs to be done right.

See below:

Proposal

An existential type is defined using the "Any<...>" construct. Existential types can be nested.

The empty existential type "Any<>" is typealiased to 'Any’.

Also, Any<SomeProtocol> is equivalent to SomeProtocol.

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).

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.

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.

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 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.

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.

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.”

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.

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.

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.

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!).

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.

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.

-Matthew

···

On May 17, 2016, at 1:52 PM, Austin Zheng <austinzheng@gmail.com> wrote:


(Adrian Zubarev) #3

+1 for extending my proposal and making `Any<>` even more a powerful beast. :slight_smile:

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Mai 2016 bei 20:52:34, Austin Zheng via swift-evolution (swift-evolution@swift.org) schrieb:

I put together the skeleton of a proposal detailing enhancements to how associated types can be referenced in Swift 3+. It's certainly not ready for submission, but I think it gets across my ideas pretty well. Would love to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how Swift developers program for years, so it needs to be done right.

See below:

Proposal

An existential type is defined using the "Any<...>" construct. Existential types can be nested.

The empty existential type "Any<>" is typealiased to 'Any'.

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.)

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.)

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.
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.

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."

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."

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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Russ Bishop) #4

I put together the skeleton of a proposal detailing enhancements to how associated types can be referenced in Swift 3+. It's certainly not ready for submission, but I think it gets across my ideas pretty well. Would love to gather feedback and especially improvements.

Austin, these comments are on the updated proposal (86d209e).

In such a case, it is not allowed to define constraints that create a relation between different existential values:
// NOT ALLOWED; each constraint can reference at most a single existential
// value (argument or return value)
func doSomething(x: Collection, y: Collection)
    where x.Element == y.Element
{
    // ...
}

What is the rationale for this restriction? I’m not arguing against it, but I’d like to understand it better… maybe the proposal should include a small explanation?

Unconditional casting to covariant generic output types (i.e. Optional<T>) is allowed, even without concrete type constraints.

Maybe it’s just me but this might warrant a little bit of explanation. Swift doesn’t have a mechanism for specifying covariant/contravariant type parameters so I assume that’s just inferred if SomeType<AssociatedType> appears in output position anywhere in the protocol? I also assume you must constrain all the type parameters of the generic type to use it this way.

Existentials cannot be used with generics in the following ways:
Passed as arguments of generic type T to generic functions, unless T is completely unconstrained and the argument's type is T or a generic type covariant on T.

I’m not quite parsing this one. If the existential is constrained enough to satisfy the function’s generic constraints why can’t the compiler call the non-specialized version? (I’m probably just misunderstanding)

A type A is a subtype of another type B iff B can be used anywhere A can without changing the semantics of the program.

Is this correct? It might be my confusion but it seems like "A type A is a subtype of B iff A can be used anywhere B can without changing the semantics of the program”. That seems to fit with the example of “String is a subtype of Streamable” because “String can be used anywhere Streamable can”.

Thanks to everyone for their work on this proposal, it looks fantastic!

Russ

···

On May 17, 2016, at 11:52 AM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:


(Austin Zheng) #5

Feel free to add as much of this proposal into yours as you want.

Austin

···

On Tue, May 17, 2016 at 12:09 PM, Adrian Zubarev via swift-evolution < swift-evolution@swift.org> wrote:

+1 for extending my proposal and making `Any<>` even more a powerful
beast. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 17. Mai 2016 bei 20:52:34, Austin Zheng via swift-evolution (
swift-evolution@swift.org) schrieb:

I put together the skeleton of a proposal detailing enhancements to how
associated types can be referenced in Swift 3+. It's certainly not ready
for submission, but I think it gets across my ideas pretty well. Would love
to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how
Swift developers program for years, so it needs to be done right.

See below:

*Proposal*

An existential type is defined using the "Any<...>" construct. Existential
types can be nested.

The empty existential type "Any<>" is typealiased to 'Any'.

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.)

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.)

   - 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.

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.

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."

   - 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."

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.

_______________________________________________
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


(Austin Zheng) #6

Responses inline. Thanks for taking the time to read through and comment,
your feedback is incredibly useful!

I put together the skeleton of a proposal detailing enhancements to how
associated types can be referenced in Swift 3+. It's certainly not ready
for submission, but I think it gets across my ideas pretty well. Would love
to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how
Swift developers program for years, so it needs to be done right.

See below:

*Proposal*

An existential type is defined using the "Any<...>" construct. Existential
types can be nested.

The empty existential type "Any<>" is typealiased to 'Any’.

Also, Any<SomeProtocol> is equivalent to SomeProtocol.

Yes. protocol<> currently also behaves like this.

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.

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.

   - 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,
> 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.

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.

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".

   - 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".

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.

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
:).

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...

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.

···

On Tue, May 17, 2016 at 12:41 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On May 17, 2016, at 1:52 PM, Austin Zheng <austinzheng@gmail.com> wrote:

-Matthew


(Austin Zheng) #7

I'm honestly not sure it makes sense to introduce a proposal just for
expressing <Class, Protocol, Protocol> style requirements, and then trying
to retrofit fuller support for other existentials onto it. I would prefer
that the 'basic package' of existential cases be considered together as a
single proposal, unless a core team member expresses their preference
otherwise.

Austin

···

On Tue, May 17, 2016 at 1:02 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On May 17, 2016, at 2:45 PM, Austin Zheng via swift-evolution < > swift-evolution@swift.org> wrote:

Feel free to add as much of this proposal into yours as you want.

The trend is towards smaller proposals and introducing change in stages.
Since Adrian’s proposal is almost ready it’s probably best to move ahead
with it as-is and follow up with generalized existentials (yours), and then
possibly a third on “opening” existentials.

Austin

On Tue, May 17, 2016 at 12:09 PM, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

+1 for extending my proposal and making `Any<>` even more a powerful
beast. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 17. Mai 2016 bei 20:52:34, Austin Zheng via swift-evolution (
swift-evolution@swift.org) schrieb:

I put together the skeleton of a proposal detailing enhancements to how
associated types can be referenced in Swift 3+. It's certainly not ready
for submission, but I think it gets across my ideas pretty well. Would love
to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how
Swift developers program for years, so it needs to be done right.

See below:

*Proposal*

An existential type is defined using the "Any<...>" construct.
Existential types can be nested.

The empty existential type "Any<>" is typealiased to 'Any'.

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.)

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.)

   - 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.

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.

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."

   - 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."

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.

_______________________________________________
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #8

I agree with this. If we're certain we should reskin protocol<> as Any<>, we should frontload that change—in addition to affecting source code, it'd also influence the runtime behavior of type printing/parsing, which can't be statically migrated in the future. I think any discussion of extending existentials has to be considered out of scope for Swift 3, though, so the Any rename deserves its own proposal.

-Joe

···

On May 17, 2016, at 1:27 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On May 17, 2016, at 3:06 PM, Austin Zheng <austinzheng@gmail.com> wrote:

I'm honestly not sure it makes sense to introduce a proposal just for expressing <Class, Protocol, Protocol> style requirements, and then trying to retrofit fuller support for other existentials onto it. I would prefer that the 'basic package' of existential cases be considered together as a single proposal, unless a core team member expresses their preference otherwise.

It also renames protocol<> to Any, but fair enough.

One reason to keep it separate is that the rename is a breaking change and we should really try to get that into Swift 3. Generalizing existentials is an additive change. I would love to have that in Swift 3 as well, but if it’s not going to make it I don’t think it should hold back the smaller change which is a breaking change.

Doug, any opinion on this?


(Austin Zheng) #9

I think this is also probably the best approach. Reskin protocol<> so
people can fix their code when 3.0 hits, and then extend its functionality
in an additive way.

Joe, given that this counts as part of "generics and ABI", is it acceptable
to continue discussion? Or should all of this be tabled until August?

Austin

···

On Tue, May 17, 2016 at 1:55 PM, Joe Groff <jgroff@apple.com> wrote:

> On May 17, 2016, at 1:27 PM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:
>
>
>> On May 17, 2016, at 3:06 PM, Austin Zheng <austinzheng@gmail.com> > wrote:
>>
>> I'm honestly not sure it makes sense to introduce a proposal just for
expressing <Class, Protocol, Protocol> style requirements, and then trying
to retrofit fuller support for other existentials onto it. I would prefer
that the 'basic package' of existential cases be considered together as a
single proposal, unless a core team member expresses their preference
otherwise.
>
> It also renames protocol<> to Any, but fair enough.
>
> One reason to keep it separate is that the rename is a breaking change
and we should really try to get that into Swift 3. Generalizing
existentials is an additive change. I would love to have that in Swift 3
as well, but if it’s not going to make it I don’t think it should hold back
the smaller change which is a breaking change.
>
> Doug, any opinion on this?

I agree with this. If we're certain we should reskin protocol<> as Any<>,
we should frontload that change—in addition to affecting source code, it'd
also influence the runtime behavior of type printing/parsing, which can't
be statically migrated in the future. I think any discussion of extending
existentials has to be considered out of scope for Swift 3, though, so the
Any rename deserves its own proposal.

-Joe


(Joe Groff) #10

I think this is also probably the best approach. Reskin protocol<> so people can fix their code when 3.0 hits, and then extend its functionality in an additive way.

Joe, given that this counts as part of "generics and ABI", is it acceptable to continue discussion? Or should all of this be tabled until August?

IMO, discussion on the list is fine, though most of us are probably too busy to deeply participate right now, and any submitted proposals are likely to be deferred until after Swift 3 wraps up.

-Joe

···

On May 17, 2016, at 2:04 PM, Austin Zheng <austinzheng@gmail.com> wrote:

Austin

On Tue, May 17, 2016 at 1:55 PM, Joe Groff <jgroff@apple.com> wrote:

> On May 17, 2016, at 1:27 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
>
>
>> On May 17, 2016, at 3:06 PM, Austin Zheng <austinzheng@gmail.com> wrote:
>>
>> I'm honestly not sure it makes sense to introduce a proposal just for expressing <Class, Protocol, Protocol> style requirements, and then trying to retrofit fuller support for other existentials onto it. I would prefer that the 'basic package' of existential cases be considered together as a single proposal, unless a core team member expresses their preference otherwise.
>
> It also renames protocol<> to Any, but fair enough.
>
> One reason to keep it separate is that the rename is a breaking change and we should really try to get that into Swift 3. Generalizing existentials is an additive change. I would love to have that in Swift 3 as well, but if it’s not going to make it I don’t think it should hold back the smaller change which is a breaking change.
>
> Doug, any opinion on this?

I agree with this. If we're certain we should reskin protocol<> as Any<>, we should frontload that change—in addition to affecting source code, it'd also influence the runtime behavior of type printing/parsing, which can't be statically migrated in the future. I think any discussion of extending existentials has to be considered out of scope for Swift 3, though, so the Any rename deserves its own proposal.

-Joe


(Matthew Johnson) #11

Feel free to add as much of this proposal into yours as you want.

The trend is towards smaller proposals and introducing change in stages. Since Adrian’s proposal is almost ready it’s probably best to move ahead with it as-is and follow up with generalized existentials (yours), and then possibly a third on “opening” existentials.

···

On May 17, 2016, at 2:45 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

Austin

On Tue, May 17, 2016 at 12:09 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1 for extending my proposal and making `Any<>` even more a powerful beast. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 17. Mai 2016 bei 20:52:34, Austin Zheng via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

I put together the skeleton of a proposal detailing enhancements to how associated types can be referenced in Swift 3+. It's certainly not ready for submission, but I think it gets across my ideas pretty well. Would love to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how Swift developers program for years, so it needs to be done right.

See below:

Proposal

An existential type is defined using the "Any<...>" construct. Existential types can be nested.

The empty existential type "Any<>" is typealiased to 'Any'.

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.)

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.)

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.
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.

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."

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."

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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Matthew Johnson) #12

Responses inline. Thanks for taking the time to read through and comment, your feedback is incredibly useful!

No problem! Glad to help.

I put together the skeleton of a proposal detailing enhancements to how associated types can be referenced in Swift 3+. It's certainly not ready for submission, but I think it gets across my ideas pretty well. Would love to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how Swift developers program for years, so it needs to be done right.

See below:

Proposal

An existential type is defined using the "Any<...>" construct. Existential types can be nested.

The empty existential type "Any<>" is typealiased to 'Any’.

Also, Any<SomeProtocol> is equivalent to SomeProtocol.

Yes. protocol<> currently also behaves like this.

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.

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. :slight_smile:

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.

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.

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"

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.

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.

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.

···

On May 17, 2016, at 3:05 PM, Austin Zheng <austinzheng@gmail.com> wrote:
On Tue, May 17, 2016 at 12:41 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On May 17, 2016, at 1:52 PM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

-Matthew


(Matthew Johnson) #13

I'm honestly not sure it makes sense to introduce a proposal just for expressing <Class, Protocol, Protocol> style requirements, and then trying to retrofit fuller support for other existentials onto it. I would prefer that the 'basic package' of existential cases be considered together as a single proposal, unless a core team member expresses their preference otherwise.

It also renames protocol<> to Any, but fair enough.

One reason to keep it separate is that the rename is a breaking change and we should really try to get that into Swift 3. Generalizing existentials is an additive change. I would love to have that in Swift 3 as well, but if it’s not going to make it I don’t think it should hold back the smaller change which is a breaking change.

Doug, any opinion on this?

···

On May 17, 2016, at 3:06 PM, Austin Zheng <austinzheng@gmail.com> wrote:

Austin

On Tue, May 17, 2016 at 1:02 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On May 17, 2016, at 2:45 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Feel free to add as much of this proposal into yours as you want.

The trend is towards smaller proposals and introducing change in stages. Since Adrian’s proposal is almost ready it’s probably best to move ahead with it as-is and follow up with generalized existentials (yours), and then possibly a third on “opening” existentials.

Austin

On Tue, May 17, 2016 at 12:09 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1 for extending my proposal and making `Any<>` even more a powerful beast. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 17. Mai 2016 bei 20:52:34, Austin Zheng via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

I put together the skeleton of a proposal detailing enhancements to how associated types can be referenced in Swift 3+. It's certainly not ready for submission, but I think it gets across my ideas pretty well. Would love to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how Swift developers program for years, so it needs to be done right.

See below:

Proposal

An existential type is defined using the "Any<...>" construct. Existential types can be nested.

The empty existential type "Any<>" is typealiased to 'Any'.

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.)

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.)

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.
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.

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."

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."

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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #14

I'm honestly not sure it makes sense to introduce a proposal just for expressing <Class, Protocol, Protocol> style requirements, and then trying to retrofit fuller support for other existentials onto it. I would prefer that the 'basic package' of existential cases be considered together as a single proposal, unless a core team member expresses their preference otherwise.

I don’t see the point why we couldn’t do that! I mean if we add to much into a single proposal it would have more chances to be rejected. Besides that Chris already did the same with his `generic typealias` proposal where the whole feature was only introduced in its base form, which will gain more constraints later on. I don’t think the base form of `Any<>` gaining more constraints will break any code later, when there are no significant changes in syntax.

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 was thinking about this while looking at `var view: Any<UIView, SomeProtocol>`. If UIView does not conform to SomeProtocol you won’t be able to construct that type because otherwise it just would be `UIView`.

One would only be able to construct `Any<>` if it can be inferred to a single type like `SomeClass`. Thats what I thought and dropped from mentioning that.

Btw. I submitted a pull request already, but I noted in my proposal in the future directions section the following:

Any<> should reflect powerful generalized generic features to be able to constrain types even further. (This should have its own proposal, which will extend Any<> proposed here.)

I hope my English wasn't too bad, please don’t blame me. :smiley:

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Mai 2016 bei 22:07:00, Austin Zheng (austinzheng@gmail.com) schrieb:

Austin

On Tue, May 17, 2016 at 1:02 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On May 17, 2016, at 2:45 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

Feel free to add as much of this proposal into yours as you want.

The trend is towards smaller proposals and introducing change in stages. Since Adrian’s proposal is almost ready it’s probably best to move ahead with it as-is and follow up with generalized existentials (yours), and then possibly a third on “opening” existentials.

Austin

On Tue, May 17, 2016 at 12:09 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
+1 for extending my proposal and making `Any<>` even more a powerful beast. :slight_smile:

--
Adrian Zubarev
Sent with Airmail

Am 17. Mai 2016 bei 20:52:34, Austin Zheng via swift-evolution (swift-evolution@swift.org) schrieb:

I put together the skeleton of a proposal detailing enhancements to how associated types can be referenced in Swift 3+. It's certainly not ready for submission, but I think it gets across my ideas pretty well. Would love to gather feedback and especially improvements.

Be unsparing; whatever form this feature takes will profoundly affect how Swift developers program for years, so it needs to be done right.

See below:

Proposal

An existential type is defined using the "Any<...>" construct. Existential types can be nested.

The empty existential type "Any<>" is typealiased to 'Any'.

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.)

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.)

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.
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.

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."

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."

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.

_______________________________________________
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Russ Bishop) #15

Its really unfortunate that the generics work is probably going to be deferred. When you really dive in to protocol-oriented programming and designing frameworks to be native Swift (taking advantage of Swift features) the existential problem comes up a lot and leads to sub-optimal designs, abandonment of type safety, or gobs of boilerplate.

Russ

···

On May 17, 2016, at 1:55 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I agree with this. If we're certain we should reskin protocol<> as Any<>, we should frontload that change—in addition to affecting source code, it'd also influence the runtime behavior of type printing/parsing, which can't be statically migrated in the future. I think any discussion of extending existentials has to be considered out of scope for Swift 3, though, so the Any rename deserves its own proposal.

-Joe


(Austin Zheng) #16

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. :slight_smile:

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,
> 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.

···

On Tue, May 17, 2016 at 1:25 PM, Matthew Johnson <matthew@anandabits.com> wrote:

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


(Austin Zheng) #17

I agree; the difference between protocols with and without associated types has been an endless source of confusion for a lot of people.

Speaking of which, for those who care I rewrote the draft proposal to attempt a much more rigorous treatment of the semantics of the generalized existential, including a discussion about existential type equivalence and subtyping. It would be nice to see people poke holes in my logic so I can patch them up. https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md

Austin

···

On May 22, 2016, at 3:05 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

On May 17, 2016, at 1:55 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I agree with this. If we're certain we should reskin protocol<> as Any<>, we should frontload that change—in addition to affecting source code, it'd also influence the runtime behavior of type printing/parsing, which can't be statically migrated in the future. I think any discussion of extending existentials has to be considered out of scope for Swift 3, though, so the Any rename deserves its own proposal.

-Joe

Its really unfortunate that the generics work is probably going to be deferred. When you really dive in to protocol-oriented programming and designing frameworks to be native Swift (taking advantage of Swift features) the existential problem comes up a lot and leads to sub-optimal designs, abandonment of type safety, or gobs of boilerplate.

Russ

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Austin Zheng) #18

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.

Austin

···

On Tue, May 17, 2016 at 9:52 PM, Austin Zheng <austinzheng@gmail.com> wrote:

On Tue, May 17, 2016 at 1:25 PM, Matthew Johnson <matthew@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. :slight_smile:

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,
> 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;
>
"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 ==
> 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


(Thorsten Seitz) #19

Shouldn't there be just a single `where` in the whole `Any<>` clause, separating the constraints on the type itself from the constraints on associated types?
This would be similar to the current use of the `where` clause in generics.

Otherwise it at least looks ambiguous whether a comma separates constraints on associated types from each other or from constraints on the type (it might still be unambiguous for the type checker).

-Thorsten

···

Am 18.05.2016 um 06:52 schrieb Austin Zheng via swift-evolution <swift-evolution@swift.org>:

On Tue, May 17, 2016 at 1:25 PM, Matthew Johnson <matthew@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.


(Matthew Johnson) #20

I agree; the difference between protocols with and without associated types has been an endless source of confusion for a lot of people.

Speaking of which, for those who care I rewrote the draft proposal to attempt a much more rigorous treatment of the semantics of the generalized existential, including a discussion about existential type equivalence and subtyping. It would be nice to see people poke holes in my logic so I can patch them up. https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md

This looks really nice. I really, really appreciate all of your hard work on this. It addresses one of the very largest pain points in Swift and does so comprehensively. (The other really large pain point IMO is lack of conditional conformance).

I haven't had time to thoroughly review every detail but didn't see errors in a relatively quick glance over it.

···

Sent from my iPad

On May 22, 2016, at 5:18 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

Austin

On May 22, 2016, at 3:05 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

On May 17, 2016, at 1:55 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I agree with this. If we're certain we should reskin protocol<> as Any<>, we should frontload that change—in addition to affecting source code, it'd also influence the runtime behavior of type printing/parsing, which can't be statically migrated in the future. I think any discussion of extending existentials has to be considered out of scope for Swift 3, though, so the Any rename deserves its own proposal.

-Joe

Its really unfortunate that the generics work is probably going to be deferred. When you really dive in to protocol-oriented programming and designing frameworks to be native Swift (taking advantage of Swift features) the existential problem comes up a lot and leads to sub-optimal designs, abandonment of type safety, or gobs of boilerplate.

Russ

_______________________________________________
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