
Swift Protocols and the Promised Land - ingve
https://realm.io/news/michele-titolo-swift-protocols-promised-land/
======
cakoose
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
Well, 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()]

