Make generics covariant and add generics to protocols

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

1 Like

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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

1+ for adding generics to protocols.

What about generics in protocols which are only a view to its associated types or generics which create/are associated types?

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
  typealias T
  typealias NodeType
  
  var value: T { get }
  var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
  typealias T // probably remove this declaration
  var value: T { get }
  var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a corresponding associated type could be synthesized making its declaration in the body of the protocol unnecessary.

In order to let

  func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

still compile there could be a general Swift feature to get the generic type by dot syntax (e.g. synthesized typealiases for every generic parameter).

The function declaration above could be rewritten to using function like parameter syntax:

    func afunction(s: SequenceType<Generator: GeneratorType<Int>, SubSequence: _>){}
    // or omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Generator: GeneratorType<Int>>){}

in this case there is almost no win. But as you can see in the example with the protocol, generics allow for much better abstraction.

Also where clauses could be used in generic parameter declarations which are disallowed for associated types.

Maximilian

···

Am 12.01.2016 um 19:19 schrieb Jordan Rose via swift-evolution <swift-evolution@swift.org>:

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

The problem is that conceptually and behaviourally Box<Bottom> *is indeed not* a Box<Top> and cannot be treated the same as one. The proposal attempts to get around this difference with a runtime failure but this would result in very fragile code - you get passed a Box<Top> and want to pass it a subclass of Top, will it succeed, who knows. You probably would be able to check the types but the complier wouldn’t highlight that this is an operation that could potentially fail.

This seems to be very much against Swift’s goal of safety being enforced by the compiler as much as possible.

Java uses the wildcard syntax to highlight this conceptual and behavioural difference - Box<Bottom> is not covariant with Box<Top> but rather with Box<? extends Top>. The compiler can then enforce that a programmer doesn’t try to pass an incompatible type to a variable of such type. Even though this is a complication to the language (many Java programmers struggle with correctly using the wildcard syntax) I don’t see covariance for generics being added to Swift in a robust manner without some kind of similar syntax.

-Simon

···

On 12 Jan 2016, at 8:45 PM, Howard Lovatt via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

@Jordan & Austin,

You raise two issues covariance and overhead of generic protocols instead
of associated types in protocols.

Having used opt in covariance in other languages, Java & Scala, I am not a
fan. You end up littering all your code with a covariance annotation. Just
look at any Java or Scala code, look at their libraries. Also when Sun ran
there project coin, vaguely similar to swift-evolution, there was a ton of
correspondence saying that people wanted covariance by default. The
official answer from Oracle was that they wished they had made covariance
the default but it was now too late to change; therefore there is strong
presidency for covariance by default.

There is some overhead with the approach I suggested, however I think that
the compiler can eliminate it. I will use the measure `sizeofValue` as a
proxy for any type of overhead (space or time). I chose this because it is
easy and because it is likely to be true that if `sizeofValue` return the
same size then the overhead is likely the same. Consider a `Boxable`
protocol and then an `Int` specialisation of that protocol.

User writes:

    protocol Boxable<T> {

        var value: Any { get set }

    }

This gets translated, as per proposal, into:

    // Size test for a generic protocol and generic struct as per proposal
and optimisation

    protocol Boxable { // User would write `protocol Boxable<T> { var
value: T { get set } }`.

        var T: Any.Type { get }

        var value: Any { get set }

    }

Then the user wants an `Int` specialisation:

    struct BoxInt: Boxable<T> {

        var value: Int

    }

This gets translated, as per proposal, into:

    struct BoxInt: Boxable { // User would write `struct BoxInt:
Boxable<Int> { var value: Int }`.

        // No need for a stored property becuse `Int` is a struct and
cannot be sub-classed.

        var T: Any.Type { // Compiler generated, user would not write
anything

            return Int.self

        }

        var _value: Any // From user written `var value: Int`.

        var value: Any {

            get {

                return _value

            }

            set {

                precondition(newValue is Int, "Type of newValue, \(newValue.
dynamicType), is not \(Int.self)")

                _value = newValue

            }

        }

        // No need for `lowestCommonDeclaredT` arg becuse `Int` is a struct
and cannot be sub-classed.

        init(value: Any) { // Compiler generated, user would not write
anything

            _value = value

        }

    }

There is some overhead:

    let bI = BoxInt(value: 1)

    sizeofValue(bI) // 32, i.e. `BoxInt (due to `value: Any`) has overhead
since an `Int` size is 8

    let boxable: Boxable = bI

    sizeofValue(boxable) // 40, i.e. generic protocol has same overhead as
non-generic protocol

Encouragingly, once you type as a protocol, whether the protocol is a
proposed generic protocol or a non-generic protocol the overhead in the
same. However `BoxInt` is certainly more overhead than `Int`.

Fortunately the compiler can optimise it away:

    struct NonGenericBoxInt { // Also generated from `struct BoxInt:
Boxable<Int> { var value: Int }`

        var value: Int

        var toBoxInt: BoxInt {

            return BoxInt(value: value)

        }

    }

    let nGBI = NonGenericBoxInt(value: 1)

    sizeofValue(nGBI) // 8, i.e. `NonGenericBoxInt has zero overhead

When the user writes `BoxInt` the compiler substitutes `NonGenericBoxInt`
and when the user covariantly assigns to the protocol `Boxable` the
compiler calls `toBoxInt`.

Therefore the original proposal could be extended to include this
optimisation.

Hopefully therefore this `efficiency` question is addressed?

Thanks for the feedback,

  -- Howard.

···

On 13 January 2016 at 05:19, Jordan Rose <jordan_rose@apple.com> wrote:

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

--
  -- Howard.

What about contravariant scenarios? Examples from C#:

IEnumerable<out T> // covariant
since if S : T then we want IEnumerable<S> : IEnumerable<T>

because IEnumerable<T> only returns T’s.

but then

IComparable<in T> // contravariant
since if S : T we definitely want IComparable<T> : IComparable<S>

because IComparable<T> only takes T’s as input.

reversing either of the above would make the type system unsound.
Any variance be default can’t be sound, and I am strongly opposed to that.

-Sune

···

On 12 Jan 2016, at 10:45, Howard Lovatt via swift-evolution <swift-evolution@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.

AFAIK traits in Rust can have both associated types and generic type
parameters. I think a corresponding feature for Swift would be a net
positive, albeit I haven't examined the tradeoffs and advantages in any
detail yet.

Austin

···

On Tue, Jan 12, 2016 at 1:16 PM, Maximilian Hünenberger < m.huenenberger@me.com> wrote:

1+ for adding generics to protocols.

What about generics in protocols which are only a view to its associated
types or generics which create/are associated types?

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
typealias T
typealias NodeType

var value: T { get }
var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
typealias T // probably remove this declaration
var value: T { get }
var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a
corresponding associated type could be synthesized making its declaration
in the body of the protocol unnecessary.

In order to let

func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

still compile there could be a general Swift feature to get the generic
type by dot syntax (e.g. synthesized typealiases for every generic
parameter).

The function declaration above could be rewritten to using function like
parameter syntax:

    func afunction(s: SequenceType<Generator: GeneratorType<Int>,
SubSequence: _>){}
    // or omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Generator: GeneratorType<Int>>){}

in this case there is almost no win. But as you can see in the example
with the protocol, generics allow for much better abstraction.

Also where clauses could be used in generic parameter declarations which
are disallowed for associated types.

Maximilian

Am 12.01.2016 um 19:19 schrieb Jordan Rose via swift-evolution < > swift-evolution@swift.org>:

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

I posted this in your original thread; I am strongly against the notion
that slightly prettier code is worth making the type system unsound, nor is
it worth the performance hit from the necessary runtime checks (even if
some fraction of them can be optimized out by the compiler).

In terms of getting rid of associated types, I think that any such proposal
should also include a description of how SequenceType is to be rewritten
using the new system, both in terms of implementation as well as usage in
APIs.

Austin

···

On Tue, Jan 12, 2016 at 5:31 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

@Jordan & Austin,

You raise two issues covariance and overhead of generic protocols instead
of associated types in protocols.

Having used opt in covariance in other languages, Java & Scala, I am not a
fan. You end up littering all your code with a covariance annotation. Just
look at any Java or Scala code, look at their libraries. Also when Sun ran
there project coin, vaguely similar to swift-evolution, there was a ton of
correspondence saying that people wanted covariance by default. The
official answer from Oracle was that they wished they had made covariance
the default but it was now too late to change; therefore there is strong
presidency for covariance by default.

There is some overhead with the approach I suggested, however I think that
the compiler can eliminate it. I will use the measure `sizeofValue` as a
proxy for any type of overhead (space or time). I chose this because it is
easy and because it is likely to be true that if `sizeofValue` return the
same size then the overhead is likely the same. Consider a `Boxable`
protocol and then an `Int` specialisation of that protocol.

User writes:

    protocol Boxable<T> {

        var value: Any { get set }

    }

This gets translated, as per proposal, into:

    // Size test for a generic protocol and generic struct as per proposal
and optimisation

    protocol Boxable { // User would write `protocol Boxable<T> { var
value: T { get set } }`.

        var T: Any.Type { get }

        var value: Any { get set }

    }

Then the user wants an `Int` specialisation:

    struct BoxInt: Boxable<T> {

        var value: Int

    }

This gets translated, as per proposal, into:

    struct BoxInt: Boxable { // User would write `struct BoxInt:
Boxable<Int> { var value: Int }`.

        // No need for a stored property becuse `Int` is a struct and
cannot be sub-classed.

        var T: Any.Type { // Compiler generated, user would not write
anything

            return Int.self

        }

        var _value: Any // From user written `var value: Int`.

        var value: Any {

            get {

                return _value

            }

            set {

                precondition(newValue is Int, "Type of newValue, \(
newValue.dynamicType), is not \(Int.self)")

                _value = newValue

            }

        }

        // No need for `lowestCommonDeclaredT` arg becuse `Int` is a
struct and cannot be sub-classed.

        init(value: Any) { // Compiler generated, user would not write
anything

            _value = value

        }

    }

There is some overhead:

    let bI = BoxInt(value: 1)

    sizeofValue(bI) // 32, i.e. `BoxInt (due to `value: Any`) has
overhead since an `Int` size is 8

    let boxable: Boxable = bI

    sizeofValue(boxable) // 40, i.e. generic protocol has same overhead
as non-generic protocol

Encouragingly, once you type as a protocol, whether the protocol is a
proposed generic protocol or a non-generic protocol the overhead in the
same. However `BoxInt` is certainly more overhead than `Int`.

Fortunately the compiler can optimise it away:

    struct NonGenericBoxInt { // Also generated from `struct BoxInt:
Boxable<Int> { var value: Int }`

        var value: Int

        var toBoxInt: BoxInt {

            return BoxInt(value: value)

        }

    }

    let nGBI = NonGenericBoxInt(value: 1)

    sizeofValue(nGBI) // 8, i.e. `NonGenericBoxInt has zero overhead

When the user writes `BoxInt` the compiler substitutes `NonGenericBoxInt`
and when the user covariantly assigns to the protocol `Boxable` the
compiler calls `toBoxInt`.

Therefore the original proposal could be extended to include this
optimisation.

Hopefully therefore this `efficiency` question is addressed?

Thanks for the feedback,

  -- Howard.

On 13 January 2016 at 05:19, Jordan Rose <jordan_rose@apple.com> wrote:

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

--
  -- Howard.

What if the “casted” value was treated as an implicitly-defined computed property? Something like this, maybe?
struct Box<T: AnyObject> {
    var value: T
    init(_ initialValue: T) {
        value = initialValue;
    }
} as T {
    get { return self.value }
    set { self.value = newValue }
}

The behavior is opt-in, simple, concise, and would allow for non-trivial cases if you were doing it for more than one type:
struct DoubleBox<T: AnyObject, U: AnyObject> {
    var t: T
    var u: U
    init(_ t: T, _: u: U) {
        self.t = t
        self.u = u
    }
} as T {
    get { return self.t }
    set { self.t = newValue }
} as U { return self.u } // is read-only when accessed as a U
- Dave

PS: Actually, in this case, since your boxed type has to be a class anyway (T: AnyObject vs T) you can *almost* do this now, if you’re willing to write some boilerplate and kind of abuse the Objective-C Bridging system:
extension Box: _ObjectiveCBridgeable {
    typealias _ObjectiveCType = T // the protocol says _ObjectiveCType: AnyObject, so any class will do
    
    static func _isBridgedToObjectiveC() -> Bool { return true }
    static func _getObjectiveCType() -> Any.Type { return _ObjectiveCType.self }
    static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: Box?) { result = Box(source) }
    static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: Box?) -> Bool {
        _forceBridgeFromObjectiveC(source, result: &result)
        return result != nil
    }
    func _bridgeToObjectiveC() -> _ObjectiveCType { return self.value }
}
Then this works now in the current version of Xcode:
let box = Box(Bottom())
let bottom = box as Bottom

Although, I’m not sure how long it will keep working, since _ObjectiveCBridgeable should probably constrain _ObjectiveCType to NSObject rather than AnyObject. Also, going the other way — converting from Bottom to Box<Bottom> — using this technique gives an error. It works with the non-generic types I was playing around with earlier, though… Maybe it’s something to do with compiler assumptions and differences between Swift’s and Objective-C’s generics system, or maybe I’m getting the function signatures close enough to satisfy the protocol requirements but somehow subtly wrong (which wouldn’t surprise me at all… sometimes I have trouble getting the details right when I’m dealing with generics in protocols).

···

On Jan 12, 2016, at 16:33, Simon Pilkington via swift-evolution <swift-evolution@swift.org> wrote:

The problem is that conceptually and behaviourally Box<Bottom> *is indeed not* a Box<Top> and cannot be treated the same as one. The proposal attempts to get around this difference with a runtime failure but this would result in very fragile code - you get passed a Box<Top> and want to pass it a subclass of Top, will it succeed, who knows. You probably would be able to check the types but the complier wouldn’t highlight that this is an operation that could potentially fail.

This seems to be very much against Swift’s goal of safety being enforced by the compiler as much as possible.

Java uses the wildcard syntax to highlight this conceptual and behavioural difference - Box<Bottom> is not covariant with Box<Top> but rather with Box<? extends Top>. The compiler can then enforce that a programmer doesn’t try to pass an incompatible type to a variable of such type. Even though this is a complication to the language (many Java programmers struggle with correctly using the wildcard syntax) I don’t see covariance for generics being added to Swift in a robust manner without some kind of similar syntax.

-Simon

On 12 Jan 2016, at 8:45 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@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

1 Like

Yes you can annotate for covariance, invariance, and contravariance, both
Java and Scala, allow all three. The problem is that the code becomes
splattered with variance annotations. The Java people themselves have
publicly regretted this and wished that they had made covariance the
default. If you look at generic code invariance and covariance are by far
the most common requirements; this proposal would address these common use
case without burdening the programmer.

Swift, and no usable language, is completely statically typed. Examples in
Swift of runtime type checking are array out of bounds and casts. There are
other examples of non-type related runtime checks is Swift: numerical
overflow, throws, using optionals to signal errors, and using enums to
signal errors. I say use what is appropriate, static type checking if it is
easy to do, otherwise runtime type checking. Note I am not proposing an
unsafe language like C, it is still type checked.

There is a strong positive precedence for a type check on write, Java
arrays (not Java `List`s). Arrays in Java may be passed covariantly, and
this is extensively used. However if you attempt to write the wrong type
into the array you will get an `ArrayStoreException`. In practice you don't
get many `ArrayStoreException`, non of my code has ever had one. Its just
not something you do in practice, as noted before contravariance is rare.

Thanks for you comments and I hope this eases your concerns,

  -- Howard.

···

On 13 January 2016 at 11:33, Simon Pilkington <simonmpilkington@icloud.com> wrote:

The problem is that conceptually and behaviourally Box<Bottom> *is indeed
not* a Box<Top> and cannot be treated the same as one. The proposal
attempts to get around this difference with a runtime failure but this
would result in very fragile code - you get passed a Box<Top> and want to
pass it a subclass of Top, will it succeed, who knows. You probably would
be able to check the types but the complier wouldn’t highlight that this is
an operation that could potentially fail.

This seems to be very much against Swift’s goal of safety being enforced
by the compiler as much as possible.

Java uses the wildcard syntax to highlight this conceptual and behavioural
difference - Box<Bottom> is not covariant with Box<Top> but rather with
Box<? extends Top>. The compiler can then enforce that a programmer doesn’t
try to pass an incompatible type to a variable of such type. Even though
this is a complication to the language (many Java programmers struggle with
correctly using the wildcard syntax) I don’t see covariance for generics
being added to Swift in a robust manner without some kind of similar syntax.

-Simon

On 12 Jan 2016, at 8:45 PM, Howard Lovatt via swift-evolution < > swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.

Would the optimisation I suggested in previous email address your concern
of needing both associated types and generics in protocols?

  -- Howard.

···

On 13 January 2016 at 08:31, Austin Zheng via swift-evolution < swift-evolution@swift.org> wrote:

AFAIK traits in Rust can have both associated types and generic type
parameters. I think a corresponding feature for Swift would be a net
positive, albeit I haven't examined the tradeoffs and advantages in any
detail yet.

Austin

On Tue, Jan 12, 2016 at 1:16 PM, Maximilian Hünenberger < > m.huenenberger@me.com> wrote:

1+ for adding generics to protocols.

What about generics in protocols which are only a view to its associated
types or generics which create/are associated types?

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
typealias T
typealias NodeType

var value: T { get }
var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
typealias T // probably remove this declaration
var value: T { get }
var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a
corresponding associated type could be synthesized making its declaration
in the body of the protocol unnecessary.

In order to let

func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

still compile there could be a general Swift feature to get the generic
type by dot syntax (e.g. synthesized typealiases for every generic
parameter).

The function declaration above could be rewritten to using function like
parameter syntax:

    func afunction(s: SequenceType<Generator: GeneratorType<Int>,
SubSequence: _>){}
    // or omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Generator: GeneratorType<Int>>){}

in this case there is almost no win. But as you can see in the example
with the protocol, generics allow for much better abstraction.

Also where clauses could be used in generic parameter declarations which
are disallowed for associated types.

Maximilian

Am 12.01.2016 um 19:19 schrieb Jordan Rose via swift-evolution < >> swift-evolution@swift.org>:

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

--
  -- Howard.

@Maximilian,

You can hand code your `Node` example using the conversions I outlined
(this is what I do in my code):

protocol Node {
var T: Any.Type
var value: T { get }
var nodeLeft: Node { get } // Changed to nodeLeft and nodeRight because
arrays aren't covariant!
var nodeRight: Node { get } // Changed to nodeLeft and nodeRight because
arrays aren't covariant!

        // One of the benefits of the proposal is that arrays would become
covariant.

}

Then when you want a `NodeInt` you would write:

struct GenericNodeInt: Node {
var T: Any.Type {

            return Int.self
        }

let value: Any

let nodeLeft: Node

let nodeRight: Node

        init(value: Any, nodeLeft: Node, nodeRight: Node) {

      precondition(value is Int, "Type of value, \(newValue.dynamicType),
is not \(Int.self)")

      self.value = value

      precondition(nodeLeft.T is Int, "Type of nodeLeft.T, \(newValue.
dynamicType), is not \(Int.self)")

      self.nodeLeft = nodeLeft

      precondition(nodeRight.T is Int, "Type of nodeRight.T, \(newValue.
dynamicType), is not \(Int.self)")

      self.nodeRight = nodeRight

        }

}

If performance was an issue you would also write:

struct NodeInt {
var T: Int.Type {

            return Int.self
        }

let value: Int

let nodeLeft: NodeInt

let nodeRight: NodeInt

        var toGenericNodeInt: GenericNodeInt {

      return GenericNodeInt(value: value, nodeLeft: nodeLeft, nodeRight:
nodeRight)

        }

}

And use `NodeInt` everywhere accept when you need the genericity and then
call `toGenericNodeInt`.

If the proposal was accepted it would take all this boilerplate code out :)

···

On 13 January 2016 at 08:16, Maximilian Hünenberger < swift-evolution@swift.org> wrote:

1+ for adding generics to protocols.

What about generics in protocols which are only a view to its associated
types or generics which create/are associated types?

Example of a simple protocol which models a node of a tree:

// Before

// NodeType can be anything
// currently Swift doesn't allow
// `typealias NodeType: Node`
//
// or even where clauses
// `typealias NodeType: Node where NodeType.T == T`
protocol Node {
typealias T
typealias NodeType

var value: T { get }
var nodes: [NodeType] { get }
}

// After
protocol Node<T> {
typealias T // probably remove this declaration
var value: T { get }
var nodes: [Node<T>] { get }
}

So a generic parameter is placed after the protocol name. Therefore a
corresponding associated type could be synthesized making its declaration
in the body of the protocol unnecessary.

In order to let

func afunction<S: SequenceType where S.Generator.Element == Int>(s: S){}

still compile there could be a general Swift feature to get the generic
type by dot syntax (e.g. synthesized typealiases for every generic
parameter).

The function declaration above could be rewritten to using function like
parameter syntax:

    func afunction(s: SequenceType<Generator: GeneratorType<Int>,
SubSequence: _>){}
    // or omitting `SubSequence: _` since the type is already unambiguous
    func afunction(s: SequenceType<Generator: GeneratorType<Int>>){}

in this case there is almost no win. But as you can see in the example
with the protocol, generics allow for much better abstraction.

Also where clauses could be used in generic parameter declarations which
are disallowed for associated types.

Maximilian

Am 12.01.2016 um 19:19 schrieb Jordan Rose via swift-evolution < > swift-evolution@swift.org>:

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

--
  -- Howard.

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

···

On 13 January 2016 at 12:37, Austin Zheng <austinzheng@gmail.com> wrote:

I posted this in your original thread; I am strongly against the notion
that slightly prettier code is worth making the type system unsound, nor is
it worth the performance hit from the necessary runtime checks (even if
some fraction of them can be optimized out by the compiler).

In terms of getting rid of associated types, I think that any such
proposal should also include a description of how SequenceType is to be
rewritten using the new system, both in terms of implementation as well as
usage in APIs.

Austin

On Tue, Jan 12, 2016 at 5:31 PM, Howard Lovatt <howard.lovatt@gmail.com> > wrote:

@Jordan & Austin,

You raise two issues covariance and overhead of generic protocols instead
of associated types in protocols.

Having used opt in covariance in other languages, Java & Scala, I am not
a fan. You end up littering all your code with a covariance annotation.
Just look at any Java or Scala code, look at their libraries. Also when Sun
ran there project coin, vaguely similar to swift-evolution, there was a ton
of correspondence saying that people wanted covariance by default. The
official answer from Oracle was that they wished they had made covariance
the default but it was now too late to change; therefore there is strong
presidency for covariance by default.

There is some overhead with the approach I suggested, however I think
that the compiler can eliminate it. I will use the measure `sizeofValue` as
a proxy for any type of overhead (space or time). I chose this because it
is easy and because it is likely to be true that if `sizeofValue` return
the same size then the overhead is likely the same. Consider a `Boxable`
protocol and then an `Int` specialisation of that protocol.

User writes:

    protocol Boxable<T> {

        var value: Any { get set }

    }

This gets translated, as per proposal, into:

    // Size test for a generic protocol and generic struct as per
proposal and optimisation

    protocol Boxable { // User would write `protocol Boxable<T> { var
value: T { get set } }`.

        var T: Any.Type { get }

        var value: Any { get set }

    }

Then the user wants an `Int` specialisation:

    struct BoxInt: Boxable<T> {

        var value: Int

    }

This gets translated, as per proposal, into:

    struct BoxInt: Boxable { // User would write `struct BoxInt:
Boxable<Int> { var value: Int }`.

        // No need for a stored property becuse `Int` is a struct and
cannot be sub-classed.

        var T: Any.Type { // Compiler generated, user would not write
anything

            return Int.self

        }

        var _value: Any // From user written `var value: Int`.

        var value: Any {

            get {

                return _value

            }

            set {

                precondition(newValue is Int, "Type of newValue, \(
newValue.dynamicType), is not \(Int.self)")

                _value = newValue

            }

        }

        // No need for `lowestCommonDeclaredT` arg becuse `Int` is a
struct and cannot be sub-classed.

        init(value: Any) { // Compiler generated, user would not write
anything

            _value = value

        }

    }

There is some overhead:

    let bI = BoxInt(value: 1)

    sizeofValue(bI) // 32, i.e. `BoxInt (due to `value: Any`) has
overhead since an `Int` size is 8

    let boxable: Boxable = bI

    sizeofValue(boxable) // 40, i.e. generic protocol has same overhead
as non-generic protocol

Encouragingly, once you type as a protocol, whether the protocol is a
proposed generic protocol or a non-generic protocol the overhead in the
same. However `BoxInt` is certainly more overhead than `Int`.

Fortunately the compiler can optimise it away:

    struct NonGenericBoxInt { // Also generated from `struct BoxInt:
Boxable<Int> { var value: Int }`

        var value: Int

        var toBoxInt: BoxInt {

            return BoxInt(value: value)

        }

    }

    let nGBI = NonGenericBoxInt(value: 1)

    sizeofValue(nGBI) // 8, i.e. `NonGenericBoxInt has zero overhead

When the user writes `BoxInt` the compiler substitutes `NonGenericBoxInt`
and when the user covariantly assigns to the protocol `Boxable` the
compiler calls `toBoxInt`.

Therefore the original proposal could be extended to include this
optimisation.

Hopefully therefore this `efficiency` question is addressed?

Thanks for the feedback,

  -- Howard.

On 13 January 2016 at 05:19, Jordan Rose <jordan_rose@apple.com> wrote:

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

--
  -- Howard.

--
  -- Howard.

@Sune,

Yes you can annotate in a C#/Scala fashion. But you have burdened the
programmer and haven't added much. In almost all cases you want covariant
and I am proposing that that requirement is so pervasive that we can just
ignore the rest. At present in Swift you only have invariant and that is
usable, if inconvenient at times.

Lets take your example of IComparable<in T> from C#. On the web page for
this, IComparable<T> Interface (System) | Microsoft Learn,
there is an example of temperature comparisons. Their own example is
invariant, because almost all comparisons will be invariant.

Furthermore Microsoft chose to make Equatable<T> invariant. A strange
choice heh. How come comparing using IComparable behaves differently than
using Equatable? If you implement both, which Microsoft recommends, then
you get invariance, because Equatable is invariant.

In Swift there is a strong emphasis on final classes and structs (that are
final). Therefore you can't use contravariance with these final types.
Therefore virtually everything is covered by covariance and I am proposing
that this should be the default. If it is found latter that there are uses
for invariant and contra variant then annotations for these could be added,
however I am suggesting that since covariant is overwhelmingly the most
useful then it should be the default.

Hope this explains my reasoning,

-- Howard.

···

On 14 January 2016 at 07:10, Sune Foldager <cyano@me.com> wrote:

On 12 Jan 2016, at 10:45, Howard Lovatt via swift-evolution < > swift-evolution@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.

What about contravariant scenarios? Examples from C#:

IEnumerable<out T> // covariant
since if S : T then we want IEnumerable<S> : IEnumerable<T>

because IEnumerable<T> only returns T’s.

but then

IComparable<in T> // contravariant
since if S : T we definitely want IComparable<T> : IComparable<S>

because IComparable<T> only takes T’s as input.

reversing either of the above would make the type system unsound.
Any variance be default can’t be sound, and I am strongly opposed to that.

-Sune

--
  -- Howard.

Java only has invariance at the generic type definition level. They allow you to use wildcards as another option to generic constraints for instance references. In effect, variance is faked by the user of a type, not defined by the author of the type.

The dynamic and unreified nature of java generics is such that several valid systems I have encountered are impossible to properly express. Likewise, several systems I have tried to debug using generics have been found to have unsound generic type systems due to the language not enforcing proper static typing.

IMHO Java is not a good language to use as an example when trying to make a case for changing the behavior of generics.

-DW

···

On Jan 12, 2016, at 6:47 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

Yes you can annotate for covariance, invariance, and contravariance, both Java and Scala, allow all three. The problem is that the code becomes splattered with variance annotations. The Java people themselves have publicly regretted this and wished that they had made covariance the default. If you look at generic code invariance and covariance are by far the most common requirements; this proposal would address these common use case without burdening the programmer.

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/\), 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@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.
...

Strong -1 from me as well for making the type system unsound.

···

Am 13.01.2016 um 02:47 schrieb Howard Lovatt via swift-evolution <swift-evolution@swift.org>:

Yes you can annotate for covariance, invariance, and contravariance, both Java and Scala, allow all three. The problem is that the code becomes splattered with variance annotations

Ceylon uses a different approach which is variance annotations at the definition site.
This restricts the possible usage of the type parameters to appropriately variant positions.

This migt be a better way to deal with variance.

-Thorsten

I’d be interested in reading up on what the Oracle response was if you have links.

As you mentioned even Swift doesn’t get away from runtime type checking but in the two examples you mentioned - array out of bounds and casts - Swift makes use of the Optionals system to highlight that an operation may fail and allows the user to handle that failure. Covariance should have similar syntax support (for example use of optional chaining similar to optional protocol requirements to indicate that a call may fail due to incorrect types). For the compiler to understand when such failure is possible, some kind of covariance syntax would be required.

As a related question, do you see covariance syntax as such a burden?

-Simon

···

On 13 Jan 2016, at 12:47 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

Yes you can annotate for covariance, invariance, and contravariance, both Java and Scala, allow all three. The problem is that the code becomes splattered with variance annotations. The Java people themselves have publicly regretted this and wished that they had made covariance the default. If you look at generic code invariance and covariance are by far the most common requirements; this proposal would address these common use case without burdening the programmer.

Swift, and no usable language, is completely statically typed. Examples in Swift of runtime type checking are array out of bounds and casts. There are other examples of non-type related runtime checks is Swift: numerical overflow, throws, using optionals to signal errors, and using enums to signal errors. I say use what is appropriate, static type checking if it is easy to do, otherwise runtime type checking. Note I am not proposing an unsafe language like C, it is still type checked.

There is a strong positive precedence for a type check on write, Java arrays (not Java `List`s). Arrays in Java may be passed covariantly, and this is extensively used. However if you attempt to write the wrong type into the array you will get an `ArrayStoreException`. In practice you don't get many `ArrayStoreException`, non of my code has ever had one. Its just not something you do in practice, as noted before contravariance is rare.

Thanks for you comments and I hope this eases your concerns,

  -- Howard.

On 13 January 2016 at 11:33, Simon Pilkington <simonmpilkington@icloud.com <mailto:simonmpilkington@icloud.com>> wrote:
The problem is that conceptually and behaviourally Box<Bottom> *is indeed not* a Box<Top> and cannot be treated the same as one. The proposal attempts to get around this difference with a runtime failure but this would result in very fragile code - you get passed a Box<Top> and want to pass it a subclass of Top, will it succeed, who knows. You probably would be able to check the types but the complier wouldn’t highlight that this is an operation that could potentially fail.

This seems to be very much against Swift’s goal of safety being enforced by the compiler as much as possible.

Java uses the wildcard syntax to highlight this conceptual and behavioural difference - Box<Bottom> is not covariant with Box<Top> but rather with Box<? extends Top>. The compiler can then enforce that a programmer doesn’t try to pass an incompatible type to a variable of such type. Even though this is a complication to the language (many Java programmers struggle with correctly using the wildcard syntax) I don’t see covariance for generics being added to Swift in a robust manner without some kind of similar syntax.

-Simon

On 12 Jan 2016, at 8:45 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.

What uses do you see for covariance defined by the author of a type?

Also I agree with you about Java; in particular for me the combination of covariance, Raw Types and Type Erasure create a very confusing system which sometimes seems to be of little benefit.

-Simon

···

On 13 Jan 2016, at 1:07 PM, David Waite <david@alkaline-solutions.com> wrote:

Java only has invariance at the generic type definition level. They allow you to use wildcards as another option to generic constraints for instance references. In effect, variance is faked by the user of a type, not defined by the author of the type.

The dynamic and unreified nature of java generics is such that several valid systems I have encountered are impossible to properly express. Likewise, several systems I have tried to debug using generics have been found to have unsound generic type systems due to the language not enforcing proper static typing.

IMHO Java is not a good language to use as an example when trying to make a case for changing the behavior of generics.

-DW

On Jan 12, 2016, at 6:47 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Yes you can annotate for covariance, invariance, and contravariance, both Java and Scala, allow all three. The problem is that the code becomes splattered with variance annotations. The Java people themselves have publicly regretted this and wished that they had made covariance the default. If you look at generic code invariance and covariance are by far the most common requirements; this proposal would address these common use case without burdening the programmer.