How Does Optional Covariance Work in Swift

How does Optional covariance work in Swift

It doesn't. Swift does not support custom covariant generics for now.

The Swift type checker is per expression, not global (as in Haskell). This task is handled by the Semantic Analysis in lib/Sema. The constraint system then tries to match the types and special cases of covariance are then handled for collections, and optionals.

This was a language design decision. You should be able to do everything you need with the built-in collection types and optionals. If you aren't you should probably open a radar.

Determine if Any.Type is Optional

Assuming that what you are trying to do is something like this:

let anyType: Any.Type = Optional<String>.self
anyType is Optional<Any>.Type // false

Sadly swift currently (as of Swift 2) does not support covariance nor contravariance and type checks directly against Optional.Type cannot be done:

// Argument for generic parameter 'Wrapped' could not be inferred
anyType is Optional.Type // Causes error

An alternative is to make Optional extend an specific protocol, and check for that type:

protocol OptionalProtocol {}

extension Optional : OptionalProtocol {}

let anyType: Any.Type = Optional<String>.self
anyType is OptionalProtocol.Type // true

Optional field type doesn't conform protocol in Swift 3

I do not believe there's a simple way to do this, given that we currently cannot talk in terms of generic types without their placeholders – therefore we cannot simply cast to Optional.Type.

Nor can we cast to Optional<Any>.Type, because the compiler doesn't provide the same kinds of automatic conversions for metatype values that it provides for instances (e.g An Optional<Int> is convertible to an Optional<Any>, but an Optional<Int>.Type is not convertible to a Optional<Any>.Type).

However one solution, albeit a somewhat hacky one, would be to define a 'dummy protocol' to represent an 'any Optional instance', regardless of the Wrapped type. We can then have this protocol define a wrappedType requirement in order to get the Wrapped metatype value for the given Optional type.

For example:

protocol OptionalProtocol {
// the metatype value for the wrapped type.
static var wrappedType: Any.Type { get }
}

extension Optional : OptionalProtocol {
static var wrappedType: Any.Type { return Wrapped.self }
}

Now if fieldMirror.subjectType is an Optional<Wrapped>.Type, we can cast it to OptionalProtocol.Type, and from there get the wrappedType metatype value. This then lets us check for CustomProtocol conformance.

for field in Mirror(reflecting: CustomClass()).children {
let fieldMirror = Mirror(reflecting: field.value)

// if fieldMirror.subjectType returns an optional metatype value
// (i.e an Optional<Wrapped>.Type), we can cast to OptionalProtocol.Type,
// and then get the Wrapped type, otherwise default to fieldMirror.subjectType
let wrappedType = (fieldMirror.subjectType as? OptionalProtocol.Type)?.wrappedType
?? fieldMirror.subjectType

// check for CustomProtocol conformance.
if wrappedType is CustomProtocol.Type {
print("\(field.label!) is \(fieldMirror.subjectType) and conforms CustomProtocol")
} else {
print("\(field.label!) is \(fieldMirror.subjectType) and DOES NOT conform CustomProtocol")
}
}

// nonoptionalField is AnotherClass and conforms CustomProtocol
// optionalField is Optional<AnotherClass> and conforms CustomProtocol

This only deals with a single level of optional nesting, but could easily be adapted to apply to an arbitrary optional nesting level through simply repeatedly attempting to cast the resultant metatype value to OptionalProtocol.Type and getting the wrappedType, and then checking for CustomProtocol conformance.

class CustomClass : CustomProtocol {
var nonoptionalField: AnotherClass = AnotherClass()
var optionalField: AnotherClass??
var str: String = ""
}

/// If `type` is an `Optional<T>` metatype, returns the metatype for `T`
/// (repeating the unwrapping if `T` is an `Optional`), along with the number of
/// times an unwrap was performed. Otherwise just `type` will be returned.
func seeThroughOptionalType(
_ type: Any.Type
) -> (wrappedType: Any.Type, layerCount: Int) {

var type = type
var layerCount = 0

while let optionalType = type as? OptionalProtocol.Type {
type = optionalType.wrappedType
layerCount += 1
}
return (type, layerCount)
}

for field in Mirror(reflecting: CustomClass()).children {

let fieldMirror = Mirror(reflecting: field.value)
let (wrappedType, _) = seeThroughOptionalType(fieldMirror.subjectType)

if wrappedType is CustomProtocol.Type {
print("\(field.label!) is \(fieldMirror.subjectType) and conforms CustomProtocol")
} else {
print("\(field.label!) is \(fieldMirror.subjectType) and DOES NOT conform CustomProtocol")
}
}
// nonoptionalField is AnotherClass and conforms CustomProtocol
// optionalField is Optional<Optional<AnotherClass>> and conforms CustomProtocol
// str is String and DOES NOT conform CustomProtocol

Overriding superclass property with different type in Swift

Swift does not allow you to change the class type of any variables or properties. Instead you can create an extra variable in the subclass that handles the new class type:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
var chassis = Chassis()
}
class RaceCar: Car {
var racingChassis = RacingChassis()
override var chassis: Chassis {
get {
return racingChassis
}
set {
if let newRacingChassis = newValue as? RacingChassis {
racingChassis = newRacingChassis
} else {
println("incorrect chassis type for racecar")
}
}
}
}

It seems one cannot declare a property with the let syntax and override it with var in it’s subclass or vice-versa, which may be because the superclass implementation might not be expecting that property to change once initialized. So in this case the property needs to be declared with ‘var’ in the superclass as well to match the subclass (as shown in the snippet above). If one cannot change the source code in the superclass then its probably best to destroy the current RaceCar and create a new RaceCar every time the chassis needs to be mutated.

Swift syntax explanation with compact map

In the call .compactMap(prepareAttributes), you pass in the function, prepareAttributes to compactMap as a closure. Since prepareAttributes takes a single input argument whose type matches the closure variable of compactMap, the compiler can automatically infer that it needs to pass $0 to prepareAttributes.

So essentially, .compactMap(prepareAttributes) is shorthand for

.compactMap {prepareAttributes(attributes: $0) }

A simple example of the same behaviour with map that is quite often used is to map over a type that you then pass into an init, which you could write as .map { MyType(input: $0) } or simplify to .map(MyType.init).

struct MyInt {
let value: Int

init(value: Int) {
self.value = value
}
}

let ints = [1,2,3]
let myInts = ints.map(MyInt.init) // same as `ints.map { MyInt(value: $0) }

Swift generic collection of Element cannot convert to collection of Any

Being able to convert from SomeType<Subtype> to SomeType<Supertype> is called covariance. In Swift, Array<T> is covariant on T by "compiler magic", and you can't do the same for your own types.

The type checker hardcodes conversions from Array to Array if there is a conversion from T to U. Similar rules exist for Optional and Dictionary. There's no mechanism for doing this with your own types.

Your own generic types are always invariant, meaning that there is never a conversion between SomeType<T> to SomeType<U>, as long as T and U are different types.

Let's imagine what would happen if the conversion on MyCollection were allowed. You could do:

let myCollectionInt = MyCollection<Int>()
let myCollectionAny: MyCollection<Any> = myCollectionInt // suppose you can do this
myCollectionAny.object = "string" // myCollectionAny.object is of type Any?, so this should be ok

We've set myCollectionAny.object to "string", but MyCollection is a reference type, so myCollectionInt.object should also be "string". But myCollectionInt.object is an Int?!

Of course this type-unsafety is also a problem with arrays, but the language designers have decided that casting arrays is a common enough thing to do, that disallowing it would do more hard than good.



Related Topics



Leave a reply



Submit