[swift-evolution] Class and Subclass Existentials (Round 2)

Matthew Johnson matthew at anandabits.com
Tue Feb 14 09:38:09 CST 2017


> On Feb 14, 2017, at 5:28 AM, David Hart <david at hartbit.com> wrote:
> 
> 
> On 14 Feb 2017, at 10:24, Slava Pestov <spestov at apple.com <mailto:spestov at apple.com>> wrote:
> 
>> 
>>> On Feb 12, 2017, at 12:32 PM, David Hart via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> Hi Matthew,
>>> 
>>> Your arguments made sense to me. I modified the proposal to choose strategy number 3: deprecating and removing class over several versions to favour AnyObject. Mind having another proof read?
>>> 
>>> https://github.com/hartbit/swift-evolution/blob/subclass-existentials/proposals/XXXX-subclass-existentials.md <https://github.com/hartbit/swift-evolution/blob/subclass-existentials/proposals/XXXX-subclass-existentials.md>
>>> 
>>> Anybody has counter arguments?
>>> 
>>> Class and Subtype existentials
>>> 
>>> Proposal: SE-XXXX <https://github.com/hartbit/swift-evolution/blob/subclass-existentials/proposals/XXXX-subclass-existentials.md>
>>> Authors: David Hart <http://github.com/hartbit/>, Austin Zheng <http://github.com/austinzheng>
>>> Review Manager: TBD
>>> Status: TBD
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#introduction>Introduction
>>> 
>>> This proposal brings more expressive power to the type system by allowing Swift to represent existentials of classes and subtypes which conform to protocols.
>>> 
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#motivation>Motivation
>>> 
>>> Currently, the only existentials which can be represented in Swift are conformances to a set of protocols, using the &protocol composition syntax:
>>> 
>>> Protocol1 & Protocol2
>>> On the other hand, Objective-C is capable of expressing existentials of classes and subclasses conforming to protocols with the following syntax:
>>> 
>>> id<Protocol1, Protocol2>
>>> Base<Protocol>*
>>> We propose to provide similar expressive power to Swift, which will also improve the bridging of those types from Objective-C.
>>> 
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#proposed-solution>Proposed solution
>>> 
>>> The proposal keeps the existing & syntax but allows the first element, and only the first, to be either the AnyObjectkeyword or of class type. The equivalent to the above Objective-C types would look like this:
>>> 
>>> AnyObject & Protocol1 & Protocol2
>>> Base & Protocol
>>> As in Objective-C, the first line is an existential of classes which conform to Protocol1 and Protocol2, and the second line is an existential of subtypes of Base which conform to Protocol.
>>> 
>>> Here are the new proposed rules for what is valid in a existential conjunction syntax:
>>> 
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#1-the-first-element-in-the-protocol-composition-syntax-can-be-the-anyobject-keyword-to-enforce-a-class-constraint>1. The first element in the protocol composition syntax can be the AnyObject keyword to enforce a class constraint:
>>> 
>>> protocol P {}
>>> struct S : P {}
>>> class C : P {}
>>> let t: P & AnyObject // Compiler error: AnyObject requirement must be in first position
>>> let u: AnyObject & P = S() // Compiler error: S is not of class type
>>> let v: AnyObject & P = C() // Compiles successfully
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#2-the-first-element-in-the-protocol-composition-syntax-can-be-a-class-type-to-enforce-the-existential-to-be-a-subtype-of-the-class>2. The first element in the protocol composition syntax can be a class type to enforce the existential to be a subtype of the class:
>>> 
>>> protocol P {}
>>> struct S {}
>>> class C {}
>>> class D : P {}
>>> class E : C, P {}
>>> let t: P & C // Compiler error: subclass constraint must be in first position
>>> let u: S & P // Compiler error: S is not of class type
>>> let v: C & P = D() // Compiler error: D is not a subtype of C
>>> let w: C & P = E() // Compiles successfully
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#3-when-a-protocol-composition-type-contains-a-typealias-the-validity-of-the-type-is-determined-using-the-following-steps>3. When a protocol composition type contains a typealias, the validity of the type is determined using the following steps:
>>> 
>>> Expand the typealias
>>> Normalize the type by removing duplicate constraints and replacing less specific constraints by more specific constraints (a class constraint is less specific than a class type constraint, which is less specific than a constraint of a subclass of that class).
>>> Check that the type does not contain two class-type constraints
>> 
>> You could generalize this and instead say that if the type contains two class-type constraints, the resulting existential type is the common base class of the two classes, or AnyObject if they do not share a common base class.
> 
> But if they share a common base class, the existential is invalid. Did I misunderstand your generalization?

Did you mean to say if they *don’t* share a common base class?

> 
>> Also, I’d like to see some discussion about class-constrained existentials appearing in the inheritance clause of a protocol. IMHO, we should ban this:
>> 
>> typealias MyType = SomeClass & SomeProtocol
>> 
>> protocol SomeOtherProtocol : MyType {}
> 
> Yep, I'll make that clear. It should be disallowed IMHO. Thanks!
> 
>> Slava
>> 
>>> class C {}
>>> class D : C {}
>>> class E {}
>>> protocol P1 {}
>>> protocol P2 {}
>>> typealias TA1 = AnyObject & P1
>>> typealias TA2 = AnyObject & P2
>>> typealias TA3 = C & P2
>>> typealias TA4 = D & P2
>>> typealias TA5 = E & P2
>>> 
>>> typealias TA5 = TA1 & TA2
>>> // Expansion: typealias TA5 = AnyObject & P1 & AnyObject & P2
>>> // Normalization: typealias TA5 = AnyObject & P1 & P2 
>>> // TA5 is valid
>>> 
>>> typealias TA6 = TA1 & TA3
>>> // Expansion: typealias TA6 = AnyObject & P1 & C & P2 
>>> // Normalization (AnyObject < C): typealias TA6 = C & P1 & P2 
>>> // TA6 is valid
>>> 
>>> typealias TA7 = TA3 & TA4
>>> // Expansion: typealias TA7 = C & P2 & D & P2
>>> // Normalization (C < D): typealias TA7 = D & P2
>>> // TA7 is valid
>>> 
>>> typealias TA8 = TA4 & TA5
>>> // Expansion: typealias TA8 = D & P2 & E & P2
>>> // Normalization: typealias TA8 = D & E & P2
>>> // TA8 is invalid because the D and E constraints are incompatible
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#class-and-anyobject>class and AnyObject
>>> 
>>> This proposal merges the concepts of class and AnyObject, which now have the same meaning: they represent an existential for classes. To get rid of the duplication, we suggest only keeping AnyObject around. To reduce source-breakage to a minimum, class could be redefined as typealias class = AnyObject and give a deprecation warning on class for the first version of Swift this proposal is implemented in. Later, class could be removed in a subsequent version of Swift.
>>> 
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#source-compatibility>Source compatibility
>>> 
>>> This change will not break Swift 3 compability mode because Objective-C types will continue to be imported as before. But in Swift 4 mode, all types bridged from Objective-C which use the equivalent Objective-C existential syntax could break code which does not meet the new protocol requirements. For example, the following Objective-C code:
>>> 
>>> @interface MyViewController
>>> - (void)setup:(nonnull UIViewController<UITableViewDataSource,UITableViewDelegate>*)tableViewController;
>>> @end
>>> is imported into Swift-3 mode as:
>>> 
>>> class MyViewController {
>>>     func setup(tableViewController: UIViewController) {}
>>> }
>>> which allows calling the function with an invalid parameter:
>>> 
>>> let myViewController: MyViewController()
>>> myViewController.setup(UIViewController())
>>> The previous code continues to compile but still crashs if the Objective-C code calls a method of UITableViewDataSource or UITableViewDelegate. But if this proposal is accepted and implemented as-is, the Objective-C code will be imported in Swift 4 mode as:
>>> 
>>> class MyViewController {
>>>     func setup(tableViewController: UIViewController & UITableViewDataSource & UITableViewDelegate) {}
>>> }
>>> That would then cause the Swift code run in version 4 mode to fail to compile with an error which states that UIViewController does not conform to the UITableViewDataSource and UITableViewDelegate protocols.
>>> 
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#alternatives-considered>Alternatives considered
>>> 
>>> An alternative solution to the class/AnyObject duplication was to keep both, redefine AnyObject as typealias AnyObject = class and favor the latter when used as a type name.
>>> 
>>>  <https://github.com/hartbit/swift-evolution/tree/subclass-existentials#acknowledgements>Acknowledgements
>>> 
>>> Thanks to Austin Zheng <http://github.com/austinzheng> and Matthew Johnson <https://github.com/anandabits> who brought a lot of attention to existentials in this mailing-list and from whom most of the ideas in the proposal come from.
>>> 
>>>> On 9 Feb 2017, at 21:50, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>>>> 
>>>> 
>>>>> On Feb 9, 2017, at 2:44 PM, David Hart <david at hartbit.com <mailto:david at hartbit.com>> wrote:
>>>>> 
>>>>> 
>>>>> On 9 Feb 2017, at 20:43, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> 
>>>>>> 
>>>>>> 
>>>>>> Sent from my iPad
>>>>>> 
>>>>>> On Feb 9, 2017, at 1:30 PM, Hooman Mehr via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>> 
>>>>>>> 
>>>>>>>> On Feb 9, 2017, at 10:47 AM, Joe Groff via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>>>>> On Feb 9, 2017, at 4:26 AM, Step Christopher via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>>>>> Looks good. Minor comments below:
>>>>>>>>> The typealias 'T5' is repeated as both an initial composition, and as a demonstration of combining typealiases. 
>>>>>>>>> 
>>>>>>>>>> This proposal merges the concepts of class and AnyObject, which now have the same meaning: they represent an existential for classes. They are four solutions to this dilemna:
>>>>>>>>>> Do nothing.
>>>>>>>>>> Replace all uses of AnyObject by class, breaking source compatibility.
>>>>>>>>>> Replace all uses of class by AnyObject, breaking source compatibility.
>>>>>>>>>> Redefine AnyObject as typealias AnyObject = class.
>>>>>>>>> I agree with other comments on recommending 4 here, and covering the others as alternatives
>>>>>>>>>>  <https://github.com/hartbit/swift-evolution/blob/e6411d8a9e7924bbd8a48fc292bf08d58a8d1199/proposals/XXXX-subclass-existentials.md#source-compatibility>I agree that we need the typealias for compatibility. I think it's still worth discussing whether the `AnyObject` typealias should *only* be there for compatibility; it could be deprecated or obsoleted in Swift 4 or future language versions.
>>>>>>> 
>>>>>>> I think it might be worth keeping to provide a more sensible capitalization alternative than lower case “class” when used as a type name:
>>>>>>> 
>>>>>>> var obj: class // this looks weird because of capitalization.
>>>>>>> 
>>>>>>> var obj: AnyObject // this looks better.
>>>>>> 
>>>>>> I agree that it looks better and would choose AnyObject if source compatibility weren't an issue.  One option that wasn't listed was to drop 'class' but use a multi-release deprecation strategy and a fix-it to facilitate a smooth transition.  If the community is willing to adopt this approach it would be my first choice.
>>>>> 
>>>>> You mean option 3?
>>>> 
>>>> Pretty much, but option 3 does not make it clear that it won’t break source immediately in Swift 4.  I think it becomes much more reasonable if Swift 3.1 code still compiles in Swift 4 mode, but with a deprecation warning.
>>>> 
>>>> The reason I prefer `AnyObject` to `class` is because I think it’s ugly to have `class` as the name of an existential type.  Type names are uppercase in Swift.  It is also used to compose with protocols which also use uppercase names in Swift.  Because it appears in contexts which use an uppercase convention it makes sense for this to have an uppercase name.  `AnyObject` seems like the obvious choice if we’re going to go in that direction.
>>>> 
>>>>> 
>>>>>>> 
>>>>>>>> 
>>>>>>>> -Joe
>>>>>>>> _______________________________________________
>>>>>>>> swift-evolution mailing list
>>>>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>>>> _______________________________________________
>>>>>>> swift-evolution mailing list
>>>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170214/2d5f871e/attachment.html>


More information about the swift-evolution mailing list