[swift-evolution] Make generics covariant and add generics to protocols

Jordan Rose jordan_rose at apple.com
Tue Jan 12 12:19:04 CST 2016


Agreed on both counts. Generics are more familiar but don't actually cover the use cases where the associated type is not independent of the model object (like a Sequence's Generator or a Collection's Index). Dropping that information results in a lot of extra indirection at runtime, which we don't want.

Jordan


> On Jan 12, 2016, at 8:17, Austin Zheng via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Strong -1, covariance on generics should be explicitly opt-in. Also -1 on generics replacing associated types in protocols.
> 
> Austin
> 
>> On Jan 12, 2016, at 1:45 AM, Howard Lovatt via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> Currently you generics are invariant whereas function arguments etc. are covariant. I am suggesting that if the way generics are implemented is changed then they can be made covariant and that this will add considerable utility to Swift generics.
>> 
>> 1st a demonstration of the current situation of invariant generics:
>> 
>>     // Current system
>>     class Top {}
>>     class Bottom: Top {}
>> 
>>     struct Box<T: AnyObject> {
>>         var value: T
>>         init(_ initialValue: T) {
>>             value = initialValue;
>>         }
>>     }
>> 
>>     let boxB = Box(Bottom())
>>     // let boxT: Box<Top> = boxB // Covariance currently not allowed
>> 
>> The key point is although `Bottom` 'is a’ `Top`, `Box<Bottom>` *is not* a `Box<Top>`.
>> 
>> I am suggesting:
>> 
>> 1. That `Box<Bottom>` should be a `Box<Top>` (covariance).
>> 2. An implementation that allows the above covariance.
>> 3. That protocols are made generic, i.e. `protocol Box<T> { var value: T { get set } }` and that this mechanism replaces associated types for protocols.
>> 
>>     // Proposal:
>>     // 1. No change to Box, i.e. programmer would just write Box as before
>>     // 2. Code transformed by comiler with write check for each specific, generic type instance
>>     // Best approximation of resulting code in current Swift to demonstrate spirit of idea:
>> 
>>     // Compiler writes a universal form using the upper bound (it writes the underlyting representation).
>>     // In practice this would be called `Box` but used `BoxAnyObject` to indicate that it has a generic argument bounded by `AnyObject`.
>>     struct BoxAnyObject {
>>         // Generated from generic argument `<T: AnyObject>`.
>>         let T: AnyObject.Type // Store the actual type.
>>         
>>         // Generated from stored property `var value: T` and noting that `T`'s upper bound is `AnyObject`.
>>         private var _value: AnyObject // Access the stored property through a setter so that type can be checked
>>         var value: AnyObject {
>>             get {
>>                 return _value
>>             }
>>             set {
>>                 // In all functions check that args declared as `T` are actually a `T` or a sub-type.
>>                 // Note: `is` only works with type literal and there is no `>=` operator for types :(.
>>                 // `is` would need changing or `>=` for types adding, nearest at moment `==`.
>>                 precondition(T == /* >= */ newValue.dynamicType, "Type of newValue, \(newValue.dynamicType), is not a sub-type of generic type T, \(T)")
>>                 _value = newValue
>>             }
>>         }
>>         
>>         // Generated from `init(_ initialValue: T)` and noting that `T`'s upper bound is `AnyObject`.
>>         init(_ lowestCommonDeclaredT: AnyObject.Type, _ initialValue: AnyObject) {
>>             T = lowestCommonDeclaredT
>>             _value = initialValue
>>         }
>>     }
>> 
>>     // Demonstrate that all `Box`es are the same size and therefore can be bitwise copied
>>     // Compiler supplies lowest-common, declared, generic type for all the `T`s in the `init` call.
>>     var bT = BoxAnyObject(Top.self, Top()) // In practice user would write `let bT = Box(Top())`.
>>     bT.T // Top.Type
>>     sizeofValue(bT) // 16
>> 
>>     var bB = BoxAnyObject(Bottom.self, Bottom()) // In practice user would write `let bB = Box(Bottom())`.
>>     bB.T // Bottom.Type
>>     sizeofValue(bB) // 16
>> 
>>     // Demonstration covariance.
>>     bT = bB // Compiler would check covariance of declared generic types.
>>     bT.T // Bottom.Type
>> 
>>     // Demonstrate generic returned type
>>     // Compiler would add cast to declared, generic type.
>>     bB.value as! Bottom // In practice user would write `bB.value`.
>> 
>>     // Demonstrate type safety
>>     bT = BoxAnyObject(Top.self, Top()) // In practice user would write `bT = Box(Top())`.
>>     bT.value = Top() // OK
>>     // bT.value = Bottom() // Doesn't work at present because need `>=` for types, but would work in practice
>>     // bB.value = Top() // Runtime error - wrong type
>> 
>> The implications of this proposal are:
>> 
>> 1. The compiler can statically type check a read from a stored property.
>> 2. A write to a stored property is type checked at runtime.
>> 3. Protocols can be made generic instead of having an associated type and then they become a proper type with dynamic dispatch.
>> 4. Generic protocols can be a type just like non-generic protocols, structs, and classes and unlike associated type protocols that can only be a generic constraint.
>> 5. The awkwardness of dealing with associated type generics is replaced by a more powerful and easier to understand semantic of a type, just like the other types.
>> 6. There is a lot of ‘non-obvoius’, long code, for example `inits`, that use a `where` clause to constrain an associated type protocol, this would be unnecessary.
>> 7. There are whole types, `AnySequence`, `AnyGenerator`, etc., that would be replaced by a generic protocols, `Sequence`, `Generator`, etc.
>> 
>> Advantages:
>> 
>> 1. Covariant generics are a powerful addition to the language.
>> 2. Generics’ invariance are inconsistent with the rest of the language.
>> 3. Generic protocols would become a ‘proper’ type and you could have arrays and fields of a generic protocol.
>> 4. There are many threads on swift-evolution looking at how protocols can be made into a ‘proper’ type or at least a concept that is easier to understand.
>> 
>> Compatibility:
>> 
>> 1. This would be a major change since associated types in protocols would be replaced by generics.
>> 2. The new implementation of generics might break some existing `struct` and `class` code, for example if it is dependent on the exact size of an object because the class will have extra fields, one for each generic type, and therefore will be larger.
>> 
>> Disadvantages:
>> 
>> 1. Major change.
>> 2. Object size increases.
>> 
>> Thanks in advance for any comments,
>> 
>>   — Howard.
>> 
>> PS This is part of a collection of proposals previously presented as “Protocols on Steroids”.
>> 
>> 
>> _______________________________________________
>> 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/20160112/c07a37f0/attachment.html>


More information about the swift-evolution mailing list