So without any initial constraints how would one use this generic function??

extension UIButton: ProtocolA {}

let button = UIButton()
let shadowedButton: ProtocolA = UIButton()

// creates a set of a least one element if the generic type could be inferred
func unionIfPossible<T, U>(_ a: T, _ b: U) -> Set<Any<T, U>>? { /* merge 
somehow if possible */ }

// this should be valid because the compiler will assume Any<UIView, ProtocolA> 
where T == UIView and U == ProtocolA
let merged_1: Set<Any<UIView, ProtocolA>> = unionIfPossible( /* UIView subtype 
*/ button, /* ProtocolA */ shadowedButton) 

// this won’t be possible because of the restriction
let merged_2: Set<Any<UIView, ProtocolA>> = unionIfPossible(shadowedButton, 
button) 

Any<UIView, ProtocolA> != Any<ProtocolA, UIView> isn’t right. Sure it may feel 
right for readability but the types should be equal.

"Can be any class type that is a UIView or a subclass of UIView, that also 
conforms to ProtocolA.“ == "Type that conforms to ProtocolA and that is a 
UIView or a subclass of UIView.“

This is also a nesting problem where you will be forced to choose the right 
place inside the angle brackets where to add a nested `Any<…>`.

class A: ClassB, ProtocolA {}

Any<A, Any<ClassB, ProtocolA>> == A != Any<Any<ClassB, ProtocolA>, A> == 
Any<ClassB, ProtocolA, A> which should be reorder by the compiler and inferred 
as A

-- 
Adrian Zubarev
Sent with Airmail

Am 18. Mai 2016 bei 21:23:35, Austin Zheng (austinzh...@gmail.com) schrieb:

I strongly prefer keeping the class requirement first, both for readability and 
because this reflects how protocol and 'class' or superclass conformances 
already work when defining classes or protocols. I think "look at the first 
argument, and then recursively look at the first arguments of any nested Any's 
for class requirements" is straightforward enough not to confuse people, and 
helps keep the type definitions organized.

Austin

On Wed, May 18, 2016 at 12:11 PM, Matthew Johnson via swift-evolution 
<swift-evolution@swift.org> wrote:

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

Okay now I feel like we’re merging everything we came up until now :D I’d love 
to see something like this happen to Swift, because `Any` seems to be a really 
powerful beast one day.

One quick question: Do we really need this "This must be the first requirement, 
if present.“?

I’m on the fence about this.  The reason it would matter is for readability.  
The counter argument to that is that you can’t rely on the first argument being 
a class to determine whether a composed Any will have a class constraint or not.


I’d say the compiler should reorder all types as it wants to. Current 
protocol<> already doing this today.

e.g.

protocol A {}
protocol B {}

typealias C = protocol<A, B>
typealias D = protocol<B, A>

print(C) // prints protocol<A, B>
print(D) // prints protocol<A, B>
print(C.self == D.self) // prints true

Basically what I mean

Any<SomeProtocol, class, AnotherProtocol>

Any<Any<ProtocolA, ProtocolB>, UIView>

should be valid.

-- 
Adrian Zubarev
Sent with Airmail

Am 18. Mai 2016 bei 18:30:02, Austin Zheng via swift-evolution 
(swift-evolution@swift.org) schrieb:

I made heavy revisions to my proposal to reflect all the great feedback I got 
from you and several other folks 
(https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md).

Here are a couple of thoughts:

- I used 'requirements' because that's how the grammar describes similar 
constructs elsewhere in Swift. Open to change, though.

- Having only one where clause makes complete sense. Protocol extensions and 
generics all use one where clause, so should this construct.

- The "P can be used in lieu of Any<P>" just means you can declare e.g. "x : 
Equatable" if you want, instead of "x : Any<Equatable>", just like you can with 
protocols without associated types or self requirements today.

- I've come to the conclusion that it's probably best to propose the proposal 
in the most general form, and allow any reviewers on the core team to excise 
parts they don't think are useful enough or are too difficult to implement. In 
that spirit, the 'simple Any<...>' construct is gone and Any usage is fully 
general.

- I moved discussion of typealiases back into the main protocol, like you said, 
because typealiases using existentials are *not* actually generic and can be 
done today.

- I added some stuff about 'narrowing' existentials at point of use using as?. 
This isn't part of 'opening existentials' and should fit in this proposal 
nicely.

- I want a type expert to look at the 'usage' section, but I'm reasonably sure 
there's not much more flexibility we can give the user. Parameter associated 
types can't be treated as covariant (they are almost always invariant or 
contravariant IIRC) and therefore they should only be accessible if fully 
bound. Return types can be treated as covariant; some languages do and some 
don't. (Swift falls into the second bucket.) I would love to be wrong, though.

Austin

On May 18, 2016, at 8:45 AM, Matthew Johnson <matt...@anandabits.com> wrote:



Sent from my iPad

On May 18, 2016, at 2:35 AM, Austin Zheng <austinzh...@gmail.com> wrote:

I've put together a considerably more detailed draft proposal, taking into 
account as much of Matthew's feedback as I could. You can find it below:

https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md

Since there is no chance this will come up for review anytime soon, I expect to 
make significant revisions to it over the next month or so. Any feedback would 
be greatly appreciated.

Thank you for working on this!  Great progress.

Minor nit, but I think the proper word is constraint rather than requirement 
here:

"Within the angle brackets < and > are zero or more requirements. Requirements 
are separated by commas."

Another tweak: 

"P can be used in lieu of Any<P>, where P is a protocol with or without 
associated type or self requirements."

This proposal is introducing generalized existentials.  P and Any<P> should be 
interchangeable for any protocol regardless of requirements of the protocol.  
Existentials of protocols with self or associated type requirements that do not 
include constraints will just expose limited direct functionality.  It would 
still be possible to attempt cast them to concrete types to recover more 
functionality.  In the future (after a follow on proposal) it will also be 
possible to open the existential.

Thorsten pointed out that there should only be one where clause for the whole 
existential.  This follows the structure of generic type and function 
constraints.  It may also be worth removing the 'as' alias from this proposal.  
This could be introduced as a stand alone proposal where it would apply to any 
context with generic constraints.

Another item:
// NOT ALLOWED let a : Any<Any<ProtocolA, ProtocolB>>

Why is this not allowed?  It is pointless, but should be allowed and considered 
identical to the flattened syntax.

On dynamic casting, I don't believe it should be restricted in the way you have 
defined here.  Casting *to* an existential doesn't have anything to do with 
opening an existential.  We should allow casting to any existential type.  

On a similar note, I completely disagree with the limitation you specify for 
use of Any in generic constraints precisely because of your counterargument.  
In the discussion about moving the where clause it has been noted that sometime 
it is necessary to apply a lot of constraints to get the necessary effect.  A 
mechanism for factoring constraints is highly desirable and will greatly 
improve the readability of generic code.  Typealiases bound to Any can provide 
such a mechanism.  Let's not artificially restrict the use of it.

The section regarding members of a partly constrained existential needs to be 
more fleshed out.  We can't simply punt it to a future proposal.  However, I do 
think it is a good idea to wait until the core team has time to participate in 
the discussion.

The section about defining typealias also should not be left to the future.  It 
is possible to define typealias with protocol<> today and to use that alias in 
a generic constraint.  Removing that capability would be a regression.  In 
fact, it's utility will increase significantly with this proposal.

In general, I don't think we need the distinction between simple and full Any.  
The whole idea of this proposal IMO should be fully generalizing existentials.  
If restrictions are necessary they should be due to (hopefully temporary) 
implementation considerations.


Austin

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


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


Within the angle brackets are zero or more 'clauses'. Clauses are separated by 
semicolons. (This is so commas can be used in where constraints, below. Better 
ideas are welcome. Maybe it's not necessary; we can use commas exclusively.)

I’m not a fan of the semicolon idea.  I don’t see any reason for this.  The 
`where` keyword separates the protocol list from the constraints just fine.  
The list on either side should be able to use commas with no problem (or line 
breaks if that proposal goes through).


I'm leaning towards getting rid of the commas, but would like to write out a 
few 'dummy' examples to see if there are any readability issues that arise. 

Replaced with what?  Whitespace separation?  I suppose that might work for the 
protocol list but it feels inconsistent with the rest of Swift.  Commas plus 
(hopefully) the alternative of newline seem like the right direction to me.

Sorry, I completely misspoke (mistyped?). I meant I want to get rid of the 
semicolons and use commas. I've come to the conclusion that there are no 
readability issues, protocol<> already uses commas, and semicolons used in this 
manner don't have a precedent anywhere else in the language.
 


There are five different possible clauses:

'class'. Must be the first clause, if present. Places a constraint on the 
existential to be any class type. (Implies: Only one can exist. Mutually 
exclusive with class name clause.)

(In the future a follow-up proposal should add in 'struct' or 'value' as a 
counterpart.)

If we’re going to allow `struct` we should also allow `enum`.  `value` would 
allow either of those.


Of course. A future proposal can allow list members to discuss the exact 
details as to how struct, value, or enum specifiers should work. 

Yep, agree.  Just mentioning that if we’re going to reference it we should not 
leave obvious holes in what would be considered.  :)

Absolutely.
 


Class name. Must be the first clause, if present. (Implies: Only one can exist. 
Mutually exclusive with 'class'.) Places a constraint on the existential (not 
really an existential anymore) to be an instance of the class, or one of its 
subclasses.
It is still be an existential if it includes protocol requirements that the 
class does not fulfill.  For example, you might have Any<UIView, SomeProtocol> 
where UIView does not conform to SomeProtocol, but various subclasses do.


Fair enough. (I don't think the way things work would be affected.)
 
Your proposal doesn’t discuss composing Any in the way that Adrian’s did like 
this:

typealias Foo = Any<SomeClass, SomeProtocol, OtherProtocol>
Any<AnotherProtocol, Foo>

I didn't think it needed to be discussed. An Any<...> existential type is a 
type 'expression' just like any other, and should be allowed to participate in 
other Any<...>s.
 

I like the idea of composition as it allows us to factor out constraints.  If 
we are going to do that we should allow a class to be specified in the 
composition as long is it is a subclass of all class requirements of Any types 
it composes.  For example, this should be allowed:

typealias Bar = Any<SubclassOfSomeClass, Foo, AnotherProtocol>

This is still one class requirement for Bar, it just refines the class 
requirement of Foo to be SubclassOfSomeClass rather than just SomeClass.

This is a good point. There should be clarification as to how special cases of 
Any<...> used in another Any<...> behave. For example, like you said 
Any<MyClass, Any<SomeSubclassOfMyClass, Protocol>> should be valid. This will 
go into any proposal that emerges from the discussion.

Yes, this is why we need to discuss Any composition.  There are also cases of 
incompatible associated type constraints which need to be rejected (such as 
composing two Any’s where one has Element == String and another has Element == 
Int).

 

Example: Any<UIViewController; UITableViewDataSource; UITableViewDelegate>
"Any UIViewController or subclass which also satisfies the table view data 
source and delegate protocols"
Dynamic protocol. This is entirely composed of the name of a protocol which has 
no associated types or Self requirement.
Example: Any<CustomStringConvertible; BooleanType>
"Any type which conforms to both the CustomStringConvertible and BooleanType 
protocols"

I'm going to use 'static protocol' to refer to a protocol with associated types 
or self requirements. Feel free to propose a more sound name.

Self-contained static protocol, simple. This is composed of the name of a 
static protocol, optionally followed by a 'where' clause in which the 
associated types can be constrained (with any of the three basic conformance 
types: subclassing, protocol conformance, or type equality). Associated types 
are referred to with a leading dot.
Please do not introduce terms “dynamic protocol” and “static protocol”.  We 
want to support existentials of protocols that have self or associated type 
requirements.  The dynamic vs static distinction is a limitation of the current 
implementation of Swift and doesn’t make sense for the long term vision.

I'm not trying to introduce new terms, these are just placeholders. At the same 
time "protocols with self or associated type requirements" is cumbersome to 
work with and it would be nice for someone to come up with a descriptive term 
of art for referring to them.

I agree that a better term would be useful.  In the meantime, I would prefer 
something like “trivial” and “nontrivial” protocols.

I've decided to just use the full name until the community comes up with better 
names. Clarity is preferable to brevity in this case.
 

 


Example: Any<Collection where .Generator.Element : NSObject, .Generator.Element 
: SomeProtocol>
"Any type that is a Collection, whose elements are NSObjects or their 
subclasses conforming to SomeProtocol.”

Swift does not allow disjunction of requirements.  Only conjunctions are 
supported.  That means the correct reading is:

"Any type that is a Collection, whose elements are NSObjects and their 
subclasses conforming to SomeProtocol.”


Yes, that is what I meant. "whose elements are (NSObjects or their subclasses) 
conforming to SomeProtocol”.

Ok, good.  Wasn’t quite clear to me.

Yes, the verbiage will need to be clearer in the future. That sentence could be 
ambiguously parsed.
 


Bound static protocol. This is the same as a self-contained static protocol, 
but with a leading "<name> as " which binds the protocol to a generic 
typealias. The name can be then be used in subsequent clauses to build 
constraints.

Example: Any<T as Collection; IntegerLiteralConvertible where 
.IntegerLiteralType == T.Element>.
"Any type that is a Collection, and also can be built from an integer literal, 
in which the collection elements are the same type as the type of the integer 
used for the integer literal conformance.”

I’m not sure about this, but if we’re going to do it it should be the other way 
around: `Collection as T` with the alias after the name of the protocol.  


I like this, it flows better. "Protocol as T where Protocol.Foo == Int, 
Protocol.Bar : Baz”.

Why did you introduce an alias here and then not use it?  Did you mean 
"Protocol as T where T.Foo == Int, T.Bar : Baz"

Another result of rushing to compose an email. Sorry!
 

 
You are also using “dot shorthand” here to refer to an associated type of 
IntegerLiteralConvertible.  I think “dot shorthand” should be limited to cases 
where there is only one protocol that is getting constrained.  In other cases, 
we need to be clear about which protocol we are referring to.

I borrowed dot shorthand from the generics manifesto. But you are right, it 
should only be allowed if there is one protocol with associated types or self 
requirements clause in the Any<...> construction.

I would actually go further and limit it to one protocol period, and possibly 
even to one protocol and no type names (as types can have nested types and 
typealiases).  When we allow shorthand it should be immediately unambiguous 
what the shorthand references with no need to look at type or protocol 
declarations.

It might be desirable to propose the proposal with no allowance for shorthand, 
and have the dot shorthand be a smaller follow-up proposal.
 

 


There will be rules to prevent recursive nesting. For example, if generic 
typealiases are allowed, they cannot refer to each other in a circular manner 
(like how structs can't contain themeselves, and you can't create a cyclic 
graph of enums containing themselves).

How an existential can be used depends on what guarantees are provided by the 
clauses. For example, 'Any<Equatable>' can't be used for much; if there were 
any methods on Equatable that did not use the associated types at all you'd be 
able to call them, but that's about it. However, 'Any<Equatable where .Self == 
String>' would allow for == to be called on instances. (This is a stupid 
example, since Any<Equatable where .Self == String> is equivalent to 'String', 
but there are almost certainly useful examples one could come up with.)

In order of increasing 'power':
Don't constrain any associated types. You can pass around Any<Equatable>s, but 
that's about it.
Constrain associated types to conform to protocols.
Fully constrain associated types.
I think we need to spell out pretty clearly what members we expect to be 
available or not available.  This section probably needs the most design and 
elaboration.  

For example, we probably can’t access a member who uses an associated type as 
an input unless it is constrained to a specific type.  On the other hand output 
types probably don’t need to limit access to a member.  However, if the output 
type is Self or an associated type the visible signature would have an output 
type which has the relevant constraints of the existential applied, but no 
more.  In some cases this means the output type would simply be Any.

Absolutely. This is vaguely what I had in mind but I wanted to get something 
down first. Thanks for thinking through some of the implications :).

That’s what I thought.  Just wanted to start the process of elaborating 
expectations.

 

Where this really gets tricky is for compound types like functions, generic 
types, etc.  Working out the details in these cases is pretty complex.  I will 
defer to Doug on whether it is best to just defer those cases to the future, 
leave them up to the implementer, or try to work out all of the relevant 
details in the proposal (in which case we probably need a type system expert to 
help!).

Yes, exactly! For example, can Any<...> existentials involving protocols with 
associated types or self requirements be used within generic function or type 
definitions? Maybe there's an argument that existential types of this nature 
are redundant if you have access to generics (e.g. defining a property on a 
generic type that is a Collection containing Ints; you should be able to do 
that today). On the other hand, maybe there are use cases I haven't thought of…

I see no reason they shouldn’t be.  They are not redundant at all.  For 
example, you may want to store instances in a heterogeneous collection.  You 
need existentials to do that.

A simple example of what I was referring to there is something like this:

protocol P {
    associatedtype Foo

    func bar(callback: (Foo) -> ())
}

In other words, types in the signature of a protocol member are complex types 
that reference Self or associated types.  I think you really need a formal 
understanding of the type system to understand how to expose these members 
through a constrained existential.  We can probably understand the expected 
behavior in some of the simpler cases on a case by case basis, but that 
approach doesn’t scale at all and is arbitrary.  If they’re going to be 
supported an expert is going to need to be involved in the design.

Yes. I have some ideas regarding this topic.
 

 

One area you didn’t touch on is “opening” the existential?  Is that out of 
scope for this proposal?  That would be fine with me as this proposal is 
already taking on a lot.  But if so, you should mention something about future 
directions as it is pretty closely related to this proposal.

Yes, existential opening is explicitly separate from this (although I wanted to 
mention it in the section where I talk about how Any<Equatable> is not very 
useful). But you are absolutely right, this proposal should discuss how it 
wants to interact with possible future directions.
 

Another area you didn’t touch on is whether Any constructs (and typealiases 
referring to them) should be usable as generic constraints.  I would expect 
this to be possible but I think we need to spell it out.

I'm hoping for community input. This is a tricky subject, and at some point 
we'll bump into implementation limitations.

I don’t think it’s too tricky.  You can just unpack the constraints of the Any 
into the list of generic constraints.  Maybe I’m missing something, but I don’t 
think so.

 

-Matthew




_______________________________________________
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


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

Reply via email to