Swift Protocols and the Promised Land
realm.io
realm.io
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.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()]