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

Austin Zheng austinzheng at gmail.com
Wed Jan 13 02:22:58 CST 2016


Hi Howard,

I appreciate the amount of thought and detail you've put into both the proposal and these replies.

However, the fundamental objection I have is that this proposal makes the type system unsound. More specifically, all your examples contain calls to precondition() and force casts, because it would become possible to write incorrectly typed code that nevertheless passes the type checker. The idea that user-level code would have to pervasively perform type checking at runtime when using generic code or risk causing a program-terminating exception goes against the design philosophy the core team has articulated for the language. Optionals and non-nullable types are (at least at first) a pain to use for a programmer used to working in a language where nil/null is a bottom type, but their presence isolates the possibility of NPEs to code that is clearly delineated by '!'. In my opinion (and others may not share this opinion), the programmer convenience gained over a system where variance is annotated explicitly is not worth introducing ways to express a new category of bugs.

Going by your implementation below:

let intBox = Box(1) // intBox is of type Box<Int>
var someBox : Box<Any> = intBox // since covariance, this is an upcast; succeeds unconditionally; could do this with a class hierarchy as well
// someBox is a copy of intBox, which means although the type system thinks it's a Box<Any>, its BoxT is still Int's type
// ...
// much later
let value : Any = "hello"
someBox.value = value // Box<Any> should accept an Any, but this will cause the precondition to fail and the program to crash
// *any* time you mutate a Box whose value isn't a bottom type your code needs to do a typecheck, or your program dies

Note that arrays in Swift are actually a covariant generic type already, but because of the way their value semantics work they are immune to this problem.

You can try to paper over this problem by introducing (e.g.) compiler magic which adjusts the metatype properties in conjunction with upcasts and downcasts, but what happens when the covariant generic type is a class and you can't rely on copy-on-assignment behavior? etc.

There are other languages that have chosen to make generic types pervasively covariant (e.g. https://www.dartlang.org/articles/why-dart-types/ <https://www.dartlang.org/articles/why-dart-types/>), but each language has its own design philosophy and I don't think this is the right direction for Swift. I think once the case has been made as to why this Swift-specific convenience-over-safety tradeoff should favor convenience, it'll be easier to discuss additional pros and cons of your specific implementation.

Best,
Austin

> On Jan 12, 2016, at 11:22 PM, Howard Lovatt <howard.lovatt at gmail.com> wrote:
> 
> @Austin,
> 
> I don't see a problem with the collections in general, that's why I chose `Box` as an example - the smallest possible collection! 
> 
> Is this what you had in mind?
> 
> //: Use a Box as an example of a minimal collection, a colection of exactly one value!
> 
> //: - note:
> //: In earlier examples I used the generic type name as the feild name directly, in practice you need a unique name.
> //: I would propose TypeName.GenericName for the unique name?
> //: In examples below I have used TypeNameGenericName as the nearest approximation possible at present.
> //: I chose upper case, but maybe lower case better?
> //: Maybe the syntax without a . is good enough?
> 
> //:     protocol Boxable<T> { var value: T { get set } }
> protocol Boxable {
>     var BoxableT: Any.Type { get }
>     var value: Any { get set }
> }
> 
> //:     protocol Generatable<T> { var generator: Generator<T> { get } }
> protocol Generatable {
>     var GeneratableT: Any.Type { get }
>     var generator: Generator { get }
> }
> 
> //:     struct Box<T>: Boxable<T>, Generatable<T> {
> //:         var value: T
> //:         var generator: Generator<T> { return BoxGenerator(box: self) }
> //:     }
> struct Box: Boxable, Generatable {
>     let BoxT: Any.Type // Box only has one 'real' generic type BoxT
>     var BoxableT: Any.Type { // BoxableT declared as same as BoxT (Boxable<T>)
>         return BoxT
>     }
>     var GeneratableT: Any.Type { // GeneratableT declared as same as BoxT (Generatable<T>)
>         return BoxT
>     }
>     
>     private var _value: Any
>     var value: Any {
>         get {
>             return _value
>         }
>         set {
>             precondition(BoxT == /* >= */ newValue.dynamicType, "Type of newValue, \(newValue.dynamicType), is not a sub-type of generic type BoxT, \(BoxT)")
>             _value = newValue
>         }
>     }
>     
>     var generator: Generator {
>         return BoxGenerator(BoxT, box: self)
>     }
>     
>     // Generated from `init(_ initialValue: T)` and noting that `T`'s upper bound is `AnyObject`.
>     init(_ lowestCommonDeclaredT: Any.Type, value: Any) {
>         BoxT = lowestCommonDeclaredT
>         _value = value
>     }
> }
> 
> //:     protocol Generator<T> { var next: T? { get } }
> protocol Generator {
>     var GeneratorT: Any.Type { get }
>     var next: Any? { get }
> }
> 
> //:     class BoxGenerator<T>: Generator<T> {
> //:         private var isFinished = false
> //:         private let box: Box<t>
> //:         var next: T? {
> //:             if isFinished { return nil }
> //:             isFinished = true
> //:             return box.value
> //:         }
> //:         init(box: Box<T>) {
> //:             self.box = box
> //:         }
> //:     }
> class BoxGenerator: Generator {
>     let BoxGeneratorT: Any.Type
>     var GeneratorT: Any.Type {
>         return BoxGeneratorT
>     }
>     private var isFinished = false
>     private let box: Box
>     var next: Any? {
>         if isFinished { return nil }
>         isFinished = true
>         return box.value
>     }
>     init(_ lowestCommonDeclaredT: Any.Type, box: Box) {
>         self.BoxGeneratorT = lowestCommonDeclaredT
>         self.box = box
>     }
> }
> 
> //:     let ints = Box(value: 1)
> let ints = Box(Int.self, value: 1) // Box<Int>, compiler determins declared type
> sizeofValue(ints) // 40
> let intsGen = ints.generator // BoxGenerator<Int>
> sizeofValue(intsGen) // 40
> intsGen.next as! Int? // 1, compiler adds cast
> intsGen.next as! Int? // nil, compiler adds cast
> 
> 
> Do you have something else in mind? Happy to take a look at *short* examples?
> 
>  -- Howard.
>  ...

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


More information about the swift-evolution mailing list