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

Howard Lovatt howard.lovatt at gmail.com
Tue Jan 12 21:57:05 CST 2016


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


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


More information about the swift-evolution mailing list