The case of broken polymorphism or "Cannot convert value of type to expected argument type"?


(Isaac Rivera) #1

Hello, list!

I am trying to find my way around Swift’s protocol limitations. It appears that in general, any protocol with declared associatedtype will break polymorphism?

Take the case below which does not compile. All "Thing” instances are "Something<VC: UIViewController>” but they can’t be passed around or coerced as so.

How is it that I can legally write the code:

class Thing: Something<UINavigationController> { }

and instantiate it, but it is not the very thing it implements?

All Thing instances conform to the public interfaces of Something<UIViewController> so why can’t they be recognized as such and coerced as such?

What is the work-around of this break in Polymorphism?

import UIKit

protocol Anything: class, NSObjectProtocol {
  
  associatedtype ViewControllerType: UIViewController
  
  var viewController: ViewControllerType { get }
  
  init(viewController: ViewControllerType)
  
  func addAnything(anything: Something<UIViewController>) -> Bool
}

class Something<VC: UIViewController>: NSObject, Anything {
  
  typealias ViewControllerType = VC
  
  private(set) var viewController: ViewControllerType
  
  required init(viewController: ViewControllerType) { self.viewController = viewController }
  
  final private var things = [String: Something<UIViewController>]()
  
  final internal func addAnything(anything: Something<UIViewController>) -> Bool {
    // implementation details...
    return true
  }
}

class Thing: Something<UINavigationController> { }

let firstThing = Thing(viewController: UINavigationController())
let secondThing = Thing(viewController: UINavigationController())

firstThing.addAnything(anything: secondThing)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to expected argument type 'Something<UIViewController>'

firstThing.addAnything(anything: secondThing as Something<UIViewController>)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to type 'Something<UIViewController>' in coercion


(Slava Pestov) #2

Hi Isaac,

This is not about associated types. Rather, the issue is that a ‘Thing’ is a ‘Something<UINavigationController>’, but you are casting it to ‘Something<UIViewController>’. The two types are not related; in general, if A is a subtype of B, then G<A> is not a subtype of G<B>.

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

Slava

···

On Feb 16, 2017, at 9:05 AM, Isaac Rivera via swift-users <swift-users@swift.org> wrote:

Hello, list!

I am trying to find my way around Swift’s protocol limitations. It appears that in general, any protocol with declared associatedtype will break polymorphism?

Take the case below which does not compile. All "Thing” instances are "Something<VC: UIViewController>” but they can’t be passed around or coerced as so.

How is it that I can legally write the code:

class Thing: Something<UINavigationController> { }

and instantiate it, but it is not the very thing it implements?

All Thing instances conform to the public interfaces of Something<UIViewController> so why can’t they be recognized as such and coerced as such?

What is the work-around of this break in Polymorphism?

import UIKit

protocol Anything: class, NSObjectProtocol {
  
  associatedtype ViewControllerType: UIViewController
  
  var viewController: ViewControllerType { get }
  
  init(viewController: ViewControllerType)
  
  func addAnything(anything: Something<UIViewController>) -> Bool
}

class Something<VC: UIViewController>: NSObject, Anything {
  
  typealias ViewControllerType = VC
  
  private(set) var viewController: ViewControllerType
  
  required init(viewController: ViewControllerType) { self.viewController = viewController }
  
  final private var things = [String: Something<UIViewController>]()
  
  final internal func addAnything(anything: Something<UIViewController>) -> Bool {
    // implementation details...
    return true
  }
}

class Thing: Something<UINavigationController> { }

let firstThing = Thing(viewController: UINavigationController())
let secondThing = Thing(viewController: UINavigationController())

firstThing.addAnything(anything: secondThing)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to expected argument type 'Something<UIViewController>'

firstThing.addAnything(anything: secondThing as Something<UIViewController>)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to type 'Something<UIViewController>' in coercion

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


(Isaac Rivera) #3

I can see it is a (counter-intuitive) language design decision for type safety… but then why, in the code below I can:

class OtherThing: Something<UIViewController> {
  override func start(_ completion: SomeCallback? = nil) {
    // implementation details...
  }
}

let firstThing = OtherThing(viewController: UINavigationController())

OtherThing extends Something<UIViewController>… but I can instantiate it with the subtype…

Ok you will say, UINavigationController is a subtype of UIViewController, but that still does not make Something<UINavigationController> a subtype of Something<UIViewController>.

Fair enough, but:

let c1: Something<UIViewController> = Something(viewController: UINavigationController())
// c1 is of type "Something<UIViewController>"

let c2 = Something(viewController: UINavigationController())
// c1 is of type "Something<UINavigationController>”

So it appears Something<UINavigationController> can be cast to type Something<UIViewController>…

Yet this is illegal?

let somethings: [Something<UIViewController>] = [c1, c2]

I dont know, something seems inconsistent.

···

On Feb 16, 2017, at 10:59 PM, Slava Pestov <spestov@apple.com> wrote:

Hi Isaac,

This is not about associated types. Rather, the issue is that a ‘Thing’ is a ‘Something<UINavigationController>’, but you are casting it to ‘Something<UIViewController>’. The two types are not related; in general, if A is a subtype of B, then G<A> is not a subtype of G<B>.

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

Slava

On Feb 16, 2017, at 9:05 AM, Isaac Rivera via swift-users <swift-users@swift.org> wrote:

Hello, list!

I am trying to find my way around Swift’s protocol limitations. It appears that in general, any protocol with declared associatedtype will break polymorphism?

Take the case below which does not compile. All "Thing” instances are "Something<VC: UIViewController>” but they can’t be passed around or coerced as so.

How is it that I can legally write the code:

class Thing: Something<UINavigationController> { }

and instantiate it, but it is not the very thing it implements?

All Thing instances conform to the public interfaces of Something<UIViewController> so why can’t they be recognized as such and coerced as such?

What is the work-around of this break in Polymorphism?

import UIKit

protocol Anything: class, NSObjectProtocol {
  
  associatedtype ViewControllerType: UIViewController
  
  var viewController: ViewControllerType { get }
  
  init(viewController: ViewControllerType)
  
  func addAnything(anything: Something<UIViewController>) -> Bool
}

class Something<VC: UIViewController>: NSObject, Anything {
  
  typealias ViewControllerType = VC
  
  private(set) var viewController: ViewControllerType
  
  required init(viewController: ViewControllerType) { self.viewController = viewController }
  
  final private var things = [String: Something<UIViewController>]()
  
  final internal func addAnything(anything: Something<UIViewController>) -> Bool {
    // implementation details...
    return true
  }
}

class Thing: Something<UINavigationController> { }

let firstThing = Thing(viewController: UINavigationController())
let secondThing = Thing(viewController: UINavigationController())

firstThing.addAnything(anything: secondThing)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to expected argument type 'Something<UIViewController>'

firstThing.addAnything(anything: secondThing as Something<UIViewController>)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to type 'Something<UIViewController>' in coercion

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


(Howard Lovatt) #4

It is confusing in Swift what can be covariant and what is invariant,
consider:

    // Covarant arrays work
    class A {}
    class B: A {}
    let a = A() // A
    let b = B() // B
    var arrayA = [a] // Array<A>
    arrayA[0] = b // OK

    // And arrays of arrays
    var arrayArrayA = [arrayA] // Array<Array<A>>
    arrayArrayA[0][0] = b // OK
    let arrayB = [b] // Array<B>
    arrayArrayA[0] = arrayB // OK, works out that an Array<B> is a Array<A>

    // Covariant homebrew-collections work
    class C<T: AnyObject> {
        var e: T
        init(_ e: T) { self.e = e }
    }
    var cA = C(a) // C<A>
    cA.e = b // OK

    // But not quite for homebrew-collections of homebrew-collections
    var cCA = C(cA) // C<C<A>>
    cCA.e.e = b // OK
    let cB = C(b) // C<B>
    // cCA.e = cB // Error - cannot work out that a C<B> is a C<A> but can
do so for arrays

It is weird that the last line fails and the equivalent Array line doesn't.
I suspect that there is some special typing going on for arrays, probably
to make them play nice with Objective-C. However it would be simpler if
everything was covariant when safe to be covariant, i.e. The last line
should work.

-- Howard.

···

On Mon, 20 Feb 2017 at 8:38 pm, Isaac Rivera via swift-users < swift-users@swift.org> wrote:

I can see it is a (counter-intuitive) language design decision for type
safety… but then why, in the code below I can:

class OtherThing: Something<UIViewController> {
override func start(_ completion: SomeCallback? = nil) {
// implementation details...
}
}

let firstThing = OtherThing(viewController: UINavigationController())

OtherThing extends Something<UIViewController>… but I can instantiate it
with the subtype…

Ok you will say, UINavigationController is a subtype of UIViewController,
but that still does not make Something<UINavigationController> a subtype
of Something<UIViewController>.

Fair enough, but:

let c1: Something<UIViewController> = Something(viewController:
UINavigationController())
// c1 is of type "Something<UIViewController>"

let c2 = Something(viewController: UINavigationController())
// c1 is of type "Something<UINavigationController>”

So it appears Something<UINavigationController> can be cast to type
Something<UIViewController>…

Yet this is illegal?

let somethings: [Something<UIViewController>] = [c1, c2]

I dont know, something seems inconsistent.

On Feb 16, 2017, at 10:59 PM, Slava Pestov <spestov@apple.com> wrote:

Hi Isaac,

This is not about associated types. Rather, the issue is that a ‘Thing’ is
a ‘Something<UINavigationController>’, but you are casting it to
‘Something<UIViewController>’. The two types are not related; in general,
if A is a subtype of B, then G<A> is not a subtype of G<B>.

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

Slava

On Feb 16, 2017, at 9:05 AM, Isaac Rivera via swift-users < > swift-users@swift.org> wrote:

Hello, list!

I am trying to find my way around Swift’s protocol limitations. It appears
that in general, any protocol with declared associatedtype will break
polymorphism?

Take the case below which does not compile. All "Thing” instances are
"Something<VC: UIViewController>” but they can’t be passed around or
coerced as so.

How is it that I can legally write the code:

class Thing: Something<UINavigationController> { }

and instantiate it, but it is not the very thing it implements?

All Thing instances conform to the public interfaces of
Something<UIViewController> so why can’t they be recognized as such and
coerced as such?

What is the work-around of this break in Polymorphism?

import UIKit

protocol Anything: class, NSObjectProtocol {

associatedtype ViewControllerType: UIViewController

var viewController: ViewControllerType { get }

init(viewController: ViewControllerType)

func addAnything(anything: Something<UIViewController>) -> Bool
}

class Something<VC: UIViewController>: NSObject, Anything {

typealias ViewControllerType = VC

private(set) var viewController: ViewControllerType

required init(viewController: ViewControllerType) { self.viewController =
viewController }

final private var things = [String: Something<UIViewController>]()

final internal func addAnything(anything: Something<UIViewController>) ->
Bool {
// implementation details...
return true
}
}

class Thing: Something<UINavigationController> { }

let firstThing = Thing(viewController: UINavigationController())
let secondThing = Thing(viewController: UINavigationController())

firstThing.addAnything(anything: secondThing)

// Playground execution failed: error: MyPlayground.playground:48:34:
error: cannot convert value of type 'Thing' to expected argument type
'Something<UIViewController>'

firstThing.addAnything(anything: secondThing as
Something<UIViewController>)

// Playground execution failed: error: MyPlayground.playground:48:34:
error: cannot convert value of type 'Thing' to type
'Something<UIViewController>' in coercion

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

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

--
-- Howard.


(Jon Shier) #5

Also possibly related is the covariance in protocol requirements. The following example doesn’t compile without casting the arrays or single values to the exact types required in the protocols, despite being covariant through protocol conformance or subclass.

protocol HasViews {
    
    var views: [UIView]!
    
}

protocol Updateable { }

extension UIView: Updateable { }

protocol HasProtocols {
    
    var updateables: [Updateable]!
    
}

class ViewController: UIViewController {
    
    @IBOutlet var views: [UIButton]!
    @IBOutlet var updateables: [UIButton]!
    
}

extension ViewController: HasViews { } // fails without casting
extension ViewController: HasProtocols { } // fails without casting

Jon

···

On Feb 20, 2017, at 7:07 PM, Howard Lovatt via swift-users <swift-users@swift.org> wrote:

It is confusing in Swift what can be covariant and what is invariant, consider:

    // Covarant arrays work
    class A {}
    class B: A {}
    let a = A() // A
    let b = B() // B
    var arrayA = [a] // Array<A>
    arrayA[0] = b // OK
    
    // And arrays of arrays
    var arrayArrayA = [arrayA] // Array<Array<A>>
    arrayArrayA[0][0] = b // OK
    let arrayB = [b] // Array<B>
    arrayArrayA[0] = arrayB // OK, works out that an Array<B> is a Array<A>
    
    // Covariant homebrew-collections work
    class C<T: AnyObject> {
        var e: T
        init(_ e: T) { self.e = e }
    }
    var cA = C(a) // C<A>
    cA.e = b // OK
    
    // But not quite for homebrew-collections of homebrew-collections
    var cCA = C(cA) // C<C<A>>
    cCA.e.e = b // OK
    let cB = C(b) // C<B>
    // cCA.e = cB // Error - cannot work out that a C<B> is a C<A> but can do so for arrays

It is weird that the last line fails and the equivalent Array line doesn't. I suspect that there is some special typing going on for arrays, probably to make them play nice with Objective-C. However it would be simpler if everything was covariant when safe to be covariant, i.e. The last line should work.

-- Howard.

On Mon, 20 Feb 2017 at 8:38 pm, Isaac Rivera via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
I can see it is a (counter-intuitive) language design decision for type safety… but then why, in the code below I can:

class OtherThing: Something<UIViewController> {
  override func start(_ completion: SomeCallback? = nil) {
    // implementation details...
  }
}

let firstThing = OtherThing(viewController: UINavigationController())

OtherThing extends Something<UIViewController>… but I can instantiate it with the subtype…

Ok you will say, UINavigationController is a subtype of UIViewController, but that still does not make Something<UINavigationController> a subtype of Something<UIViewController>.

Fair enough, but:

let c1: Something<UIViewController> = Something(viewController: UINavigationController())
// c1 is of type "Something<UIViewController>"

let c2 = Something(viewController: UINavigationController())
// c1 is of type "Something<UINavigationController>”

So it appears Something<UINavigationController> can be cast to type Something<UIViewController>…

Yet this is illegal?

let somethings: [Something<UIViewController>] = [c1, c2]

I dont know, something seems inconsistent.

On Feb 16, 2017, at 10:59 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

Hi Isaac,

This is not about associated types. Rather, the issue is that a ‘Thing’ is a ‘Something<UINavigationController>’, but you are casting it to ‘Something<UIViewController>’. The two types are not related; in general, if A is a subtype of B, then G<A> is not a subtype of G<B>.

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) <https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)>

Slava

On Feb 16, 2017, at 9:05 AM, Isaac Rivera via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hello, list!

I am trying to find my way around Swift’s protocol limitations. It appears that in general, any protocol with declared associatedtype will break polymorphism?

Take the case below which does not compile. All "Thing” instances are "Something<VC: UIViewController>” but they can’t be passed around or coerced as so.

How is it that I can legally write the code:

class Thing: Something<UINavigationController> { }

and instantiate it, but it is not the very thing it implements?

All Thing instances conform to the public interfaces of Something<UIViewController> so why can’t they be recognized as such and coerced as such?

What is the work-around of this break in Polymorphism?

import UIKit

protocol Anything: class, NSObjectProtocol {
  
  associatedtype ViewControllerType: UIViewController
  
  var viewController: ViewControllerType { get }
  
  init(viewController: ViewControllerType)
  
  func addAnything(anything: Something<UIViewController>) -> Bool
}

class Something<VC: UIViewController>: NSObject, Anything {
  
  typealias ViewControllerType = VC
  
  private(set) var viewController: ViewControllerType
  
  required init(viewController: ViewControllerType) { self.viewController = viewController }
  
  final private var things = [String: Something<UIViewController>]()
  
  final internal func addAnything(anything: Something<UIViewController>) -> Bool {
    // implementation details...
    return true
  }
}

class Thing: Something<UINavigationController> { }

let firstThing = Thing(viewController: UINavigationController())
let secondThing = Thing(viewController: UINavigationController())

firstThing.addAnything(anything: secondThing)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to expected argument type 'Something<UIViewController>'

firstThing.addAnything(anything: secondThing as Something<UIViewController>)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to type 'Something<UIViewController>' in coercion

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users
--
-- Howard.
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Slava Pestov) #6

It is confusing in Swift what can be covariant and what is invariant, consider:

    // Covarant arrays work
    class A {}
    class B: A {}
    let a = A() // A
    let b = B() // B
    var arrayA = [a] // Array<A>
    arrayA[0] = b // OK
    
    // And arrays of arrays
    var arrayArrayA = [arrayA] // Array<Array<A>>
    arrayArrayA[0][0] = b // OK
    let arrayB = [b] // Array<B>
    arrayArrayA[0] = arrayB // OK, works out that an Array<B> is a Array<A>

There are implicit conversions from Array<T> to Array<U> if T < U, and similarly for Optional, so they’re covariant in this sense (but it’s an explicit runtime conversion behind the scenes).

    // Covariant homebrew-collections work
    class C<T: AnyObject> {
        var e: T
        init(_ e: T) { self.e = e }
    }
    var cA = C(a) // C<A>
    cA.e = b // OK

This is actually not covariance, you’re just upcasting ‘b’ to ‘a’ before calling the setter for cA.e. ‘cA’ is still a C<A>, not a C<B>.

    // But not quite for homebrew-collections of homebrew-collections
    var cCA = C(cA) // C<C<A>>
    cCA.e.e = b // OK
    let cB = C(b) // C<B>
    // cCA.e = cB // Error - cannot work out that a C<B> is a C<A> but can do so for arrays

It is weird that the last line fails and the equivalent Array line doesn't. I suspect that there is some special typing going on for arrays, probably to make them play nice with Objective-C. However it would be simpler if everything was covariant when safe to be covariant, i.e. The last line should work.

The problem is determining if something is safe. For example,

class Base {}
class Derived : Base {}

struct Covariant<T> {
  func returns() -> T { … }
}

Here, you would want Covariant<Derived> to be a subtype of Covariant<Base>, because Covariant<Derived>.returns produces a Derived, which is certainly a subtype of Base.

This is contravariant though:

struct Contravariant<T> {
  func takes(_: T)
}

Contravariant<Base> is now a subtype of Contravariant<Derived>, not the other way around (proof is an exercise for the reader).

Ok, so that’s all very well and good, and we could in fact infer this from looking at the types of the members. However, if the library author adds new members, or you add new members in an extension, a generic parameter that was previously covariant or contravariant could now become invariant, breaking source compatibility and ABI.

Also, there is the issue of representation changes. If a concrete type C conforms to a protocol P, then we model C as a subtype of P, but at runtime a conversion is required and the two types have values with different size, etc. I’m not sure how we would model this in general.

So I’d rather draw a line in the sand and say that Swift generics should never support variance, except for the simple cases of arrays and optionals which are baked into the type checker.

Slava

···

On Feb 20, 2017, at 4:07 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

-- Howard.

On Mon, 20 Feb 2017 at 8:38 pm, Isaac Rivera via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
I can see it is a (counter-intuitive) language design decision for type safety… but then why, in the code below I can:

class OtherThing: Something<UIViewController> {
  override func start(_ completion: SomeCallback? = nil) {
    // implementation details...
  }
}

let firstThing = OtherThing(viewController: UINavigationController())

OtherThing extends Something<UIViewController>… but I can instantiate it with the subtype…

Ok you will say, UINavigationController is a subtype of UIViewController, but that still does not make Something<UINavigationController> a subtype of Something<UIViewController>.

Fair enough, but:

let c1: Something<UIViewController> = Something(viewController: UINavigationController())
// c1 is of type "Something<UIViewController>"

let c2 = Something(viewController: UINavigationController())
// c1 is of type "Something<UINavigationController>”

So it appears Something<UINavigationController> can be cast to type Something<UIViewController>…

Yet this is illegal?

let somethings: [Something<UIViewController>] = [c1, c2]

I dont know, something seems inconsistent.

On Feb 16, 2017, at 10:59 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

Hi Isaac,

This is not about associated types. Rather, the issue is that a ‘Thing’ is a ‘Something<UINavigationController>’, but you are casting it to ‘Something<UIViewController>’. The two types are not related; in general, if A is a subtype of B, then G<A> is not a subtype of G<B>.

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) <https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)>

Slava

On Feb 16, 2017, at 9:05 AM, Isaac Rivera via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hello, list!

I am trying to find my way around Swift’s protocol limitations. It appears that in general, any protocol with declared associatedtype will break polymorphism?

Take the case below which does not compile. All "Thing” instances are "Something<VC: UIViewController>” but they can’t be passed around or coerced as so.

How is it that I can legally write the code:

class Thing: Something<UINavigationController> { }

and instantiate it, but it is not the very thing it implements?

All Thing instances conform to the public interfaces of Something<UIViewController> so why can’t they be recognized as such and coerced as such?

What is the work-around of this break in Polymorphism?

import UIKit

protocol Anything: class, NSObjectProtocol {
  
  associatedtype ViewControllerType: UIViewController
  
  var viewController: ViewControllerType { get }
  
  init(viewController: ViewControllerType)
  
  func addAnything(anything: Something<UIViewController>) -> Bool
}

class Something<VC: UIViewController>: NSObject, Anything {
  
  typealias ViewControllerType = VC
  
  private(set) var viewController: ViewControllerType
  
  required init(viewController: ViewControllerType) { self.viewController = viewController }
  
  final private var things = [String: Something<UIViewController>]()
  
  final internal func addAnything(anything: Something<UIViewController>) -> Bool {
    // implementation details...
    return true
  }
}

class Thing: Something<UINavigationController> { }

let firstThing = Thing(viewController: UINavigationController())
let secondThing = Thing(viewController: UINavigationController())

firstThing.addAnything(anything: secondThing)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to expected argument type 'Something<UIViewController>'

firstThing.addAnything(anything: secondThing as Something<UIViewController>)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to type 'Something<UIViewController>' in coercion

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users
--
-- Howard.


(Slava Pestov) #7

Also possibly related is the covariance in protocol requirements. The following example doesn’t compile without casting the arrays or single values to the exact types required in the protocols, despite being covariant through protocol conformance or subclass.

protocol HasViews {
    
    var views: [UIView]!
    
}

protocol Updateable { }

extension UIView: Updateable { }

protocol HasProtocols {
    
    var updateables: [Updateable]!
    
}

class ViewController: UIViewController {
    
    @IBOutlet var views: [UIButton]!
    @IBOutlet var updateables: [UIButton]!
    
}

extension ViewController: HasViews { } // fails without casting
extension ViewController: HasProtocols { } // fails without casting

This kind of thing we do want to support eventually, I think.

Method overrides support a limited form of variance — you can make method parameters more optional in an override, or change their type to a base class; similarly a method return type can becomes less optional or change to a subclass. Overrides do not support converting to a protocol existential though.

Protocol requirements do not support variance at all, except for @objc protocols, which allow the same optionality changes as method overrides (but no class upcasts!)

This is all quite confusing to remember, and it would be great to see a proposal that makes the rules more general and consistent between method overrides in classes and protocol requirement witnesses.

Slava

···

On Feb 20, 2017, at 4:17 PM, Jon Shier <jon@jonshier.com> wrote:

Jon

On Feb 20, 2017, at 7:07 PM, Howard Lovatt via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

It is confusing in Swift what can be covariant and what is invariant, consider:

    // Covarant arrays work
    class A {}
    class B: A {}
    let a = A() // A
    let b = B() // B
    var arrayA = [a] // Array<A>
    arrayA[0] = b // OK
    
    // And arrays of arrays
    var arrayArrayA = [arrayA] // Array<Array<A>>
    arrayArrayA[0][0] = b // OK
    let arrayB = [b] // Array<B>
    arrayArrayA[0] = arrayB // OK, works out that an Array<B> is a Array<A>
    
    // Covariant homebrew-collections work
    class C<T: AnyObject> {
        var e: T
        init(_ e: T) { self.e = e }
    }
    var cA = C(a) // C<A>
    cA.e = b // OK
    
    // But not quite for homebrew-collections of homebrew-collections
    var cCA = C(cA) // C<C<A>>
    cCA.e.e = b // OK
    let cB = C(b) // C<B>
    // cCA.e = cB // Error - cannot work out that a C<B> is a C<A> but can do so for arrays

It is weird that the last line fails and the equivalent Array line doesn't. I suspect that there is some special typing going on for arrays, probably to make them play nice with Objective-C. However it would be simpler if everything was covariant when safe to be covariant, i.e. The last line should work.

-- Howard.

On Mon, 20 Feb 2017 at 8:38 pm, Isaac Rivera via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
I can see it is a (counter-intuitive) language design decision for type safety… but then why, in the code below I can:

class OtherThing: Something<UIViewController> {
  override func start(_ completion: SomeCallback? = nil) {
    // implementation details...
  }
}

let firstThing = OtherThing(viewController: UINavigationController())

OtherThing extends Something<UIViewController>… but I can instantiate it with the subtype…

Ok you will say, UINavigationController is a subtype of UIViewController, but that still does not make Something<UINavigationController> a subtype of Something<UIViewController>.

Fair enough, but:

let c1: Something<UIViewController> = Something(viewController: UINavigationController())
// c1 is of type "Something<UIViewController>"

let c2 = Something(viewController: UINavigationController())
// c1 is of type "Something<UINavigationController>”

So it appears Something<UINavigationController> can be cast to type Something<UIViewController>…

Yet this is illegal?

let somethings: [Something<UIViewController>] = [c1, c2]

I dont know, something seems inconsistent.

On Feb 16, 2017, at 10:59 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

Hi Isaac,

This is not about associated types. Rather, the issue is that a ‘Thing’ is a ‘Something<UINavigationController>’, but you are casting it to ‘Something<UIViewController>’. The two types are not related; in general, if A is a subtype of B, then G<A> is not a subtype of G<B>.

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) <https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)>

Slava

On Feb 16, 2017, at 9:05 AM, Isaac Rivera via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hello, list!

I am trying to find my way around Swift’s protocol limitations. It appears that in general, any protocol with declared associatedtype will break polymorphism?

Take the case below which does not compile. All "Thing” instances are "Something<VC: UIViewController>” but they can’t be passed around or coerced as so.

How is it that I can legally write the code:

class Thing: Something<UINavigationController> { }

and instantiate it, but it is not the very thing it implements?

All Thing instances conform to the public interfaces of Something<UIViewController> so why can’t they be recognized as such and coerced as such?

What is the work-around of this break in Polymorphism?

import UIKit

protocol Anything: class, NSObjectProtocol {
  
  associatedtype ViewControllerType: UIViewController
  
  var viewController: ViewControllerType { get }
  
  init(viewController: ViewControllerType)
  
  func addAnything(anything: Something<UIViewController>) -> Bool
}

class Something<VC: UIViewController>: NSObject, Anything {
  
  typealias ViewControllerType = VC
  
  private(set) var viewController: ViewControllerType
  
  required init(viewController: ViewControllerType) { self.viewController = viewController }
  
  final private var things = [String: Something<UIViewController>]()
  
  final internal func addAnything(anything: Something<UIViewController>) -> Bool {
    // implementation details...
    return true
  }
}

class Thing: Something<UINavigationController> { }

let firstThing = Thing(viewController: UINavigationController())
let secondThing = Thing(viewController: UINavigationController())

firstThing.addAnything(anything: secondThing)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to expected argument type 'Something<UIViewController>'

firstThing.addAnything(anything: secondThing as Something<UIViewController>)

// Playground execution failed: error: MyPlayground.playground:48:34: error: cannot convert value of type 'Thing' to type 'Something<UIViewController>' in coercion

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users
--
-- Howard.
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users