3 ms·
A lot of the generics/covariance complaints seem to be rooted in a misunderstanding of covariance. class Fruit {} class Peach: Fruit {} protocol FruitHo
by cakoose 11y ago
A lot of the generics/covariance complaints seem to be rooted in a misunderstanding of covariance.
class Fruit {}
class Peach: Fruit {}
protocol FruitHolder {
var fruits: [Fruit] { get }
}
class PeachBasket: FruitHolder {
var fruits: [Peach] = []
}
This doesn't compile because it is not type-safe. Even though "Peach" is a subtype of "Fruit", "[Peach]" is not a subtype of "[Fruit]".
If the above code were allowed, we could circumvent the type system:
var h: FruitHolder = PeachBasket();
var fruits = h.fruits;
fruits.append(Fruit());
I just added a non-Peach to a "PeachBasket". When someone iterates through it later, expecting it to be all "Peach" instances, they're going to be surprised.
This reminds me of when Java 1.5 introduced generics. People were used to arrays being covariant even though that's not type-safe. The language designers decided not to carry that particular type system loophole over to generics, and people (including me) were surprised by the new behavior. (And then once we wrapped our heads around it, even more surprised we had never noticed the issue before!)
Objective-C had a bunch of holes in the type system and people got used to them. Swift is trying to be more type-safe. Interestingly, Dart made the conscious decision to retain this particular type system hole; their generics are always covariant.
BTW, Java's "wildcards" (existential types) get you roughly what you want in a type-safe way:
protocol FruitHolder {
var fruits: [? : Fruit] { get }
}
This roughly means that "fruits" is an array of some subtype "Fruit", but it might not necessarily be "Fruit". Then, doing "fruits.append(Fruit())" would fail to compile but you could still read from the array.
- leftspin 11y agoWell, I think the semantics of your class hierarchy are not what you expect. You're saying FruitHolder can hold any kind of Fruit but then you're trying to "take back" that definition in PeachBasket where you're saying "just kidding, it only holds one kind of Fruit". It's better if you explicitly start with the definition that a FruitHolder can only store a type of Fruit, but then use inheritance to abstract your fruits. This works fine: class Fruit {} class Peach: Fruit {} protocol FruitHolder { typealias FruitKind var fruits: [FruitKind] { get } } class PeachBasket: FruitHolder { typealias FruitKind = Peach var fruits: [FruitKind] = [] } class FruitBasket: FruitHolder { typealias FruitKind = Fruit var fruits: [FruitKind] = [] } var fruitBasket = FruitBasket() fruitBasket.fruits += [Fruit(), Peach()] var peachBasket = PeachBasket() // ERROR: peachBasket.fruits += [Fruit(), Peach()]