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 <[email protected]> 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 
>> <[email protected] <mailto:[email protected]>> 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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to