[swift-users] The case of broken polymorphism or "Cannot convert value of type to expected argument type"?
Slava Pestov
spestov at apple.com
Tue Feb 21 03:37:58 CST 2017
> On Feb 20, 2017, at 4:07 PM, Howard Lovatt <howard.lovatt at gmail.com> 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>
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
>
> -- Howard.
>
> On Mon, 20 Feb 2017 at 8:38 pm, Isaac Rivera via swift-users <swift-users at swift.org <mailto:swift-users at 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 at apple.com <mailto:spestov at 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 at swift.org <mailto:swift-users at 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 at swift.org <mailto:swift-users at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-users <https://lists.swift.org/mailman/listinfo/swift-users>
>>
>
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org <mailto:swift-users at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-users <https://lists.swift.org/mailman/listinfo/swift-users>
> --
> -- Howard.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170221/4374cda3/attachment.html>
More information about the swift-users
mailing list