Generic types—covariance/contravariance


(Braeden Profile) #1

Has the core team or the community considered the possibility of implementing covariant/contravariant generic types? It would really be appreciated.

I know with Array, vague-ifying or specific-ifying the type ([Int] to [Any]) has help from the compiler—and we can use `map` if all else fails—but that only lessens the impact of the missing functionality. This is my exact use case here, using SceneKit to identify the first-hit Controller object:

class ControllerNode<Controller: AnyObject>: SCNNode
{
  let controller: Controller
}

// Ordered front to back, returns the first Controller object.
for hit in hitTest
{
  // Determine if this node is part of a controller.
  let ancestrySequence = sequence(first: hit.node, next: { $0.parent })
  let lastControllerNode: ControllerNode<AnyObject>? = ancestrySequence.reduce(nil)
    { ($1 as? ControllerNode) ?? $0 }
  
  if let cabinet = lastControllerNode?.controller as? CabinetController
    { return cabinet }
  
  if let wall = lastControllerNode?.controller as? WallController
    { return wall }
}

This compiles, but unfortunately, this will never work. The `reduce` algorithm always ends up trying to convert things like `ControllerNode<WallController> as ControllerNode<AnyObject>`, which—unintuitively—always fails. Without compiler help, so would things like `myIntArray as [Any]` or `Optional<Boy>(Boy()) as Optional<Human>`.

If Swift is supposed to welcome generic programming, this would be a great thing to have.


(Daniel Leping) #2

Huge +1 here.

To bring powerful functional approach to Swift I strongly feel like it's a
must have.

Issue demonstration:
//co/invariance type
typealias FooFun<-A, +R> = (A)->R
//current types
typealias BarFun<A, R> = (A)->R

func exact(view:UIView) -> UIView
func nonexact(view:NSObject) -> UIControl

//ok
let magicFoo1:FooFun<UIView, UIView> = exact
//ok
let magicFoo2:FooFun<UIView, UIView> = nonexact

//ok
let magicBar1:BarFun<UIView, UIView> = exact
//FAIL
let magicBar2:BarFun<UIView, UIView> = nonexact

The last one fails, though function nonexact can do the job perfectly and
there is no inconsistency as it accepts NSObject (UIView inherits from it
so we are safe) and gives back UIControl (which is a subclass of UIView).

There are other examples with classes and functional stuff, but I hope I
made the problem clear.

···

On Fri, 9 Dec 2016 at 2:46 Braeden Profile via swift-evolution < swift-evolution@swift.org> wrote:

Has the core team or the community considered the possibility of
implementing covariant/contravariant generic types? It would really be
appreciated.

I know with Array, vague-ifying or specific-ifying the type ([Int] to
[Any]) has help from the compiler—and we can use `map` if all else
fails—but that only lessens the impact of the missing functionality. This
is my exact use case here, using SceneKit to identify the first-hit
Controller object:

class ControllerNode<Controller: AnyObject>: SCNNode
{
let controller: Controller
}

// Ordered front to back, returns the first Controller object.
for hit in hitTest
{
// Determine if this node is part of a controller.
let ancestrySequence = sequence(first: hit.node, next: { $0.parent })
* let lastControllerNode: ControllerNode<AnyObject>? =
ancestrySequence.reduce(nil)*
* { ($1 as? ControllerNode) ?? $0 }*
if let cabinet = lastControllerNode?.controller as? CabinetController
{ return cabinet }

if let wall = lastControllerNode?.controller as? WallController
{ return wall }
}

This compiles, but unfortunately, this will never work. The `reduce`
algorithm always ends up trying to convert things like
`ControllerNode<WallController> as ControllerNode<AnyObject>`,
which—unintuitively—always fails. Without compiler help, so would things
like `myIntArray as [Any]` or `Optional<Boy>(Boy()) as Optional<Human>`.

If Swift is supposed to welcome generic programming, this would be a great
thing to have.
_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution


(Robert Widmann) #3

I keep seeing collections brought up whenever this is discussed, so my question is: Have you found a broader use for variance annotations in the Swift you write? Even in Objective-C (perhaps due to their relative obscurity) I don't see (non-Foundation) people make use of the variance annotations for generic classes, so it makes me think we could just implement this as an extension to the collection casting machinery instead of exposing a Scala-esque variance notation. Sort of like how it was done before when subtyping was introduced for function types around 2.x IIRC.

~Robert Widmann

2016/12/08 19:45、Braeden Profile via swift-evolution <swift-evolution@swift.org> のメッセージ:

···

Has the core team or the community considered the possibility of implementing covariant/contravariant generic types? It would really be appreciated.

I know with Array, vague-ifying or specific-ifying the type ([Int] to [Any]) has help from the compiler—and we can use `map` if all else fails—but that only lessens the impact of the missing functionality. This is my exact use case here, using SceneKit to identify the first-hit Controller object:

class ControllerNode<Controller: AnyObject>: SCNNode
{
  let controller: Controller
}

// Ordered front to back, returns the first Controller object.
for hit in hitTest
{
  // Determine if this node is part of a controller.
  let ancestrySequence = sequence(first: hit.node, next: { $0.parent })
  let lastControllerNode: ControllerNode<AnyObject>? = ancestrySequence.reduce(nil)
    { ($1 as? ControllerNode) ?? $0 }
  
  if let cabinet = lastControllerNode?.controller as? CabinetController
    { return cabinet }
  
  if let wall = lastControllerNode?.controller as? WallController
    { return wall }
}

This compiles, but unfortunately, this will never work. The `reduce` algorithm always ends up trying to convert things like `ControllerNode<WallController> as ControllerNode<AnyObject>`, which—unintuitively—always fails. Without compiler help, so would things like `myIntArray as [Any]` or `Optional<Boy>(Boy()) as Optional<Human>`.

If Swift is supposed to welcome generic programming, this would be a great thing to have.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charles Srstka) #4

Yes, please.

+1.

Charles

···

On Dec 8, 2016, at 6:45 PM, Braeden Profile via swift-evolution <swift-evolution@swift.org> wrote:

Has the core team or the community considered the possibility of implementing covariant/contravariant generic types? It would really be appreciated.

I know with Array, vague-ifying or specific-ifying the type ([Int] to [Any]) has help from the compiler—and we can use `map` if all else fails—but that only lessens the impact of the missing functionality. This is my exact use case here, using SceneKit to identify the first-hit Controller object:

class ControllerNode<Controller: AnyObject>: SCNNode
{
  let controller: Controller
}

// Ordered front to back, returns the first Controller object.
for hit in hitTest
{
  // Determine if this node is part of a controller.
  let ancestrySequence = sequence(first: hit.node, next: { $0.parent })
  let lastControllerNode: ControllerNode<AnyObject>? = ancestrySequence.reduce(nil)
    { ($1 as? ControllerNode) ?? $0 }
  
  if let cabinet = lastControllerNode?.controller as? CabinetController
    { return cabinet }
  
  if let wall = lastControllerNode?.controller as? WallController
    { return wall }
}

This compiles, but unfortunately, this will never work. The `reduce` algorithm always ends up trying to convert things like `ControllerNode<WallController> as ControllerNode<AnyObject>`, which—unintuitively—always fails. Without compiler help, so would things like `myIntArray as [Any]` or `Optional<Boy>(Boy()) as Optional<Human>`.

If Swift is supposed to welcome generic programming, this would be a great thing to have.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Hooman Mehr) #5

For the specific case of custom collections, I think it is worth providing a protocol as Doug noted before.

Quoting Doug Gregor (1/13/16, thread: "Make generics covariant and add generics to protocols”):

Swift’s value-semantic collections are covariant in their generic parameters, which we do through some fairly tight coupling between the compiler and standard library. From a theoretical standpoint, I’m very happy with the way value-semantic collections provide subtyping and mutation while maintaining soundness (== no runtime checks needed), and for me I would consider it “enough” if we were to formalize that compiler/collection type interaction with some kind of protocol so other collection types could opt in to subtyping, because I don’t think variance—as a language feature—carries its weight outside of the fairly narrow collection-subtyping cases.

(Emphasis mine)

I also agree with Doug and you that variance does not carry its weight outside of collection-subtyping cases.

···

On Dec 9, 2016, at 10:41 AM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

I keep seeing collections brought up whenever this is discussed, so my question is: Have you found a broader use for variance annotations in the Swift you write? Even in Objective-C (perhaps due to their relative obscurity) I don't see (non-Foundation) people make use of the variance annotations for generic classes, so it makes me think we could just implement this as an extension to the collection casting machinery instead of exposing a Scala-esque variance notation. Sort of like how it was done before when subtyping was introduced for function types around 2.x IIRC.

~Robert Widmann

2016/12/08 19:45、Braeden Profile via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

Has the core team or the community considered the possibility of implementing covariant/contravariant generic types? It would really be appreciated.

I know with Array, vague-ifying or specific-ifying the type ([Int] to [Any]) has help from the compiler—and we can use `map` if all else fails—but that only lessens the impact of the missing functionality. This is my exact use case here, using SceneKit to identify the first-hit Controller object:

class ControllerNode<Controller: AnyObject>: SCNNode
{
  let controller: Controller
}

// Ordered front to back, returns the first Controller object.
for hit in hitTest
{
  // Determine if this node is part of a controller.
  let ancestrySequence = sequence(first: hit.node, next: { $0.parent })
  let lastControllerNode: ControllerNode<AnyObject>? = ancestrySequence.reduce(nil)
    { ($1 as? ControllerNode) ?? $0 }
  
  if let cabinet = lastControllerNode?.controller as? CabinetController
    { return cabinet }
  
  if let wall = lastControllerNode?.controller as? WallController
    { return wall }
}

This compiles, but unfortunately, this will never work. The `reduce` algorithm always ends up trying to convert things like `ControllerNode<WallController> as ControllerNode<AnyObject>`, which—unintuitively—always fails. Without compiler help, so would things like `myIntArray as [Any]` or `Optional<Boy>(Boy()) as Optional<Human>`.

If Swift is supposed to welcome generic programming, this would be a great thing to have.
_______________________________________________
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


(Paul Cantrell) #6

Siesta’s Entity would certainly profit from being able to opt in to this mechanism:

    https://bustoutsolutions.github.io/siesta/api/Structs/Entity.html

It is _not_ a collection type and does not implement any collection-related protocols. However, the mechanism would (I think?) be essentially the same as for collections. So I’m not sure whether this is an example for or against Doug’s remarks that you quoted.

Cheers, P

···

On Dec 9, 2016, at 1:24 PM, Hooman Mehr via swift-evolution <swift-evolution@swift.org> wrote:

For the specific case of custom collections, I think it is worth providing a protocol as Doug noted before.

Quoting Doug Gregor (1/13/16, thread: "Make generics covariant and add generics to protocols”):

Swift’s value-semantic collections are covariant in their generic parameters, which we do through some fairly tight coupling between the compiler and standard library. From a theoretical standpoint, I’m very happy with the way value-semantic collections provide subtyping and mutation while maintaining soundness (== no runtime checks needed), and for me I would consider it “enough” if we were to formalize that compiler/collection type interaction with some kind of protocol so other collection types could opt in to subtyping, because I don’t think variance—as a language feature—carries its weight outside of the fairly narrow collection-subtyping cases.

(Emphasis mine)

I also agree with Doug and you that variance does not carry its weight outside of collection-subtyping cases.

On Dec 9, 2016, at 10:41 AM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I keep seeing collections brought up whenever this is discussed, so my question is: Have you found a broader use for variance annotations in the Swift you write? Even in Objective-C (perhaps due to their relative obscurity) I don't see (non-Foundation) people make use of the variance annotations for generic classes, so it makes me think we could just implement this as an extension to the collection casting machinery instead of exposing a Scala-esque variance notation. Sort of like how it was done before when subtyping was introduced for function types around 2.x IIRC.

~Robert Widmann

2016/12/08 19:45、Braeden Profile via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

Has the core team or the community considered the possibility of implementing covariant/contravariant generic types? It would really be appreciated.

I know with Array, vague-ifying or specific-ifying the type ([Int] to [Any]) has help from the compiler—and we can use `map` if all else fails—but that only lessens the impact of the missing functionality. This is my exact use case here, using SceneKit to identify the first-hit Controller object:

class ControllerNode<Controller: AnyObject>: SCNNode
{
  let controller: Controller
}

// Ordered front to back, returns the first Controller object.
for hit in hitTest
{
  // Determine if this node is part of a controller.
  let ancestrySequence = sequence(first: hit.node, next: { $0.parent })
  let lastControllerNode: ControllerNode<AnyObject>? = ancestrySequence.reduce(nil)
    { ($1 as? ControllerNode) ?? $0 }
  
  if let cabinet = lastControllerNode?.controller as? CabinetController
    { return cabinet }
  
  if let wall = lastControllerNode?.controller as? WallController
    { return wall }
}

This compiles, but unfortunately, this will never work. The `reduce` algorithm always ends up trying to convert things like `ControllerNode<WallController> as ControllerNode<AnyObject>`, which—unintuitively—always fails. Without compiler help, so would things like `myIntArray as [Any]` or `Optional<Boy>(Boy()) as Optional<Human>`.

If Swift is supposed to welcome generic programming, this would be a great thing to have.
_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Waite) #7

I wouldn’t keep it that narrow - monadic types like Optional also benefit from variance:

func p(_ data:Any?) {
  if data != nil {
    data.map { print($0) }
  }
}
var a:String? = "foo"
p(a)
// -> “foo"

-DW

···

On Dec 9, 2016, at 12:24 PM, Hooman Mehr via swift-evolution <swift-evolution@swift.org> wrote:

For the specific case of custom collections, I think it is worth providing a protocol as Doug noted before.

Quoting Doug Gregor (1/13/16, thread: "Make generics covariant and add generics to protocols”):

Swift’s value-semantic collections are covariant in their generic parameters, which we do through some fairly tight coupling between the compiler and standard library. From a theoretical standpoint, I’m very happy with the way value-semantic collections provide subtyping and mutation while maintaining soundness (== no runtime checks needed), and for me I would consider it “enough” if we were to formalize that compiler/collection type interaction with some kind of protocol so other collection types could opt in to subtyping, because I don’t think variance—as a language feature—carries its weight outside of the fairly narrow collection-subtyping cases.

(Emphasis mine)

I also agree with Doug and you that variance does not carry its weight outside of collection-subtyping cases.

-DW


(Hooman Mehr) #8

As Braeden notes, the same compiler magic already works for most standard library container / monadic types. It could probably work in more cases.
Let me clarify what I meant:

This compiler magic is a very useful machinery that already exists and although the use cases are not wide enough to “pollute” the whole language, they are important enough to warrant making this compiler machinery public and usable by other framework authors besides the standard library.

···

On Dec 10, 2016, at 8:18 AM, David Waite <david@alkaline-solutions.com> wrote:

I wouldn’t keep it that narrow - monadic types like Optional also benefit from variance:

func p(_ data:Any?) {
  if data != nil {
    data.map { print($0) }
  }
}
var a:String? = "foo"
p(a)
// -> “foo"

-DW

On Dec 9, 2016, at 12:24 PM, Hooman Mehr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

For the specific case of custom collections, I think it is worth providing a protocol as Doug noted before.

Quoting Doug Gregor (1/13/16, thread: "Make generics covariant and add generics to protocols”):

Swift’s value-semantic collections are covariant in their generic parameters, which we do through some fairly tight coupling between the compiler and standard library. From a theoretical standpoint, I’m very happy with the way value-semantic collections provide subtyping and mutation while maintaining soundness (== no runtime checks needed), and for me I would consider it “enough” if we were to formalize that compiler/collection type interaction with some kind of protocol so other collection types could opt in to subtyping, because I don’t think variance—as a language feature—carries its weight outside of the fairly narrow collection-subtyping cases.

(Emphasis mine)

I also agree with Doug and you that variance does not carry its weight outside of collection-subtyping cases.

-DW


(Braeden Profile) #9

I did some experimenting, and I see that most variance situations are covered by the compiler. Generic collections and wrappers, like Optional or Array, are acceptable with .map and .flatMap, but currently benefit from compiler magic to cast those types. However, not all the standard library generic types are convertible because they don’t all carry (the right) .map or .flatMap. Observe:

struct GenericType<T>
{
  let t: T
  init(_ t: T)
    { self.t = t }
  
  func map<U>(by transform: (T) -> U) -> GenericType<U>
    { return GenericType<U>(transform(t)) }
  func flatMap<U>(by transform: (T) -> U?) -> GenericType<U>?
    { return transform(t).map { GenericType<U>($0) } }
}

// let g = something using `as` and variance (or compiler magic) to cast.
// let G = something without using `as` or variance to cast.

// "Upcasting" the wrapped type: All should return successfully cast types, as an Int is always an Any. Casting with `as` always makes sense here.

let a = Optional<Int>(0) as Optional<Any> // This compiler magic is used absolutely everywhere.
let A = Optional<Int>(0).map { $0 } as Optional<Any> // Acceptable.

let b = Array<Int>() as Array<Any> // This compiler magic sometimes uses NSArray unexpectedly.
let B = Array<Int>().map { $0 } as Array<Any> // Acceptable.

let c = GenericType<Int>(0) as? GenericType<Any> // Without compiler magic, requires `as?` and never succeeds.
let C = GenericType<Int>(0).map { $0 } as GenericType<Any> // Acceptable.

// "Downcasting" the wrapped type: Returns `nil` if the cast fails (where the wrapped Any isn't an Int). Casting with `as` usually makes sense.

let x = Optional<Any>(0) as? Optional<Int> // Returns Optional<Int>, not Optional<Int>?. This is different from the other two's desired result.
let X = Optional<Any>(0).flatMap { $0 as? Int } // Using map would return Optional<Int>?.

struct SomeError: Error { }
let y = Array<Any>([4, ""]) as? Array<Int>
let Y = try? Array<Any>([4, ""]).map { try ($0 as? Int) ?? { throw SomeError() }() } // I don't have any idea how to accomplish that well. flatMap doesn't do the same thing as returning Array<Int>? if one element fails.

let z = GenericType<Any>(0) as? GenericType<Int> // Without compiler magic, always fails.
let Z = GenericType<Any>(0).flatMap { $0 as? Int } // Does every generic type really need to implement a `map` and `flatMap` for polymorphism to exist?

// * Ranges——a standard library type with some serious problems. * //

class Number: Comparable
{
  // Stores a double. Implementation is hidden.
}

class RationalNumber: Number
  { /* Extra functionality */ }

let rationalRange: Range<RationalNumber> = .init(1.0) ..< .init(2.0)
let numberRange: Range<Number> = rationalRange // There's no .map to fix this problem!

Obviously, there is some room for improvement in polymorphism among generic types. Upcasting, I think, is always a trivial matter and could be supported. Downcasting is less cut and dry, but would still make sense if we could improve the situation we have. Having to use .map and .flatMap every time you want to polymorphize any generic type—besides the compiler’s babies—is a pain. What do you guys think?

Whether or not we find some sort of improvement here, I still wonder how I could identify my generic type from a parent type:

let any: Any = GenericType<Int>

if let generic = any as? GenericType<Any> // Currently fails for any GenericType<Int>, GenericType<String>, etc.
  { /* Do something with my generic type */ }

···

On Dec 10, 2016, at 9:18 AM, David Waite <david@alkaline-solutions.com> wrote:

I wouldn’t keep it that narrow - monadic types like Optional also benefit from variance:

func p(_ data:Any?) {
  if data != nil {
    data.map { print($0) }
  }
}
var a:String? = "foo"
p(a)
// -> “foo"

-DW

On Dec 9, 2016, at 12:24 PM, Hooman Mehr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

For the specific case of custom collections, I think it is worth providing a protocol as Doug noted before.

Quoting Doug Gregor (1/13/16, thread: "Make generics covariant and add generics to protocols”):

Swift’s value-semantic collections are covariant in their generic parameters, which we do through some fairly tight coupling between the compiler and standard library. From a theoretical standpoint, I’m very happy with the way value-semantic collections provide subtyping and mutation while maintaining soundness (== no runtime checks needed), and for me I would consider it “enough” if we were to formalize that compiler/collection type interaction with some kind of protocol so other collection types could opt in to subtyping, because I don’t think variance—as a language feature—carries its weight outside of the fairly narrow collection-subtyping cases.

(Emphasis mine)

I also agree with Doug and you that variance does not carry its weight outside of collection-subtyping cases.

-DW


(Jon Hull) #10

+1

···

On Dec 10, 2016, at 1:11 PM, Hooman Mehr via swift-evolution <swift-evolution@swift.org> wrote:

As Braeden notes, the same compiler magic already works for most standard library container / monadic types. It could probably work in more cases.
Let me clarify what I meant:

This compiler magic is a very useful machinery that already exists and although the use cases are not wide enough to “pollute” the whole language, they are important enough to warrant making this compiler machinery public and usable by other framework authors besides the standard library.

On Dec 10, 2016, at 8:18 AM, David Waite <david@alkaline-solutions.com <mailto:david@alkaline-solutions.com>> wrote:

I wouldn’t keep it that narrow - monadic types like Optional also benefit from variance:

func p(_ data:Any?) {
  if data != nil {
    data.map { print($0) }
  }
}
var a:String? = "foo"
p(a)
// -> “foo"

-DW

On Dec 9, 2016, at 12:24 PM, Hooman Mehr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

For the specific case of custom collections, I think it is worth providing a protocol as Doug noted before.

Quoting Doug Gregor (1/13/16, thread: "Make generics covariant and add generics to protocols”):

Swift’s value-semantic collections are covariant in their generic parameters, which we do through some fairly tight coupling between the compiler and standard library. From a theoretical standpoint, I’m very happy with the way value-semantic collections provide subtyping and mutation while maintaining soundness (== no runtime checks needed), and for me I would consider it “enough” if we were to formalize that compiler/collection type interaction with some kind of protocol so other collection types could opt in to subtyping, because I don’t think variance—as a language feature—carries its weight outside of the fairly narrow collection-subtyping cases.

(Emphasis mine)

I also agree with Doug and you that variance does not carry its weight outside of collection-subtyping cases.

-DW

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution