Lifting the "Self or associated type" constraint on existentials

I'm struggling to grasp all the language theory involved in this, so sorry for any misunderstandings, I'll have a try.

First, Joe brought up the covariance and contravariance (feel free to point out more suitable resource). An attempt at layman's example:

// Covariance
class Base { func foo() {} }
class Derived: Base { func bar() {} }

let base: Base = Derived() // ok, since Derived is subclass of base, i.e. they are covariant
base.foo()  

// Contravariance
protocol Shape { var area: Double { get } }
struct Circle: Shape { 
    var radius: Double
    var area: Double { return radius * radius * .pi }
}

// printer only knows about Shapes, not Circles
func printShape(shape: Shape) { print(shape.area) }

func looper(circles: [Circle], operation: (Circle) -> Void) {
  for circle in circles { operation(circle) }
}

let circles = [Circle(radius: 1), Circle(radius: 2)]
// can pass a function that only knows about Shapes to
// parameter *operation* that expects Circle type, 
// because of contravariance
looper(circles: circles, operation: printShape)  

So with that out of the way, the issue seems to be that

  • Regular (constraint only) protocols are both covariant and contravariant when used as existentials (i.e. instances of protocol type), whereas
  • some instantiations of self or associated type protocols (existentials) cannot provide those same guarantees.

Hence:

I believe Dave when he says that “P does not conform to P” (i.e. issues with co- and contravariance) can never be fully fixed/solved for self or associated type protocols when used as existentials. However, even if we cannot get 100% there, I think there's huge opportunity for developers and huge value in using them even if they only work e.g. 50% of the time or e.g. 80% of the use cases. Cutting these possibilities out completely, just because 100% is not possible, feels like handcuffing the developers.


EDIT:

So by tweaking the language and how existentials work, the “P does not conform to P” issue could even go away?


In the above example, adding associated type Area to protocol Shape, and then typealiasing Area to Double in the Circle struct would be an example of the kind of evolution that developer does to code, but it crashes on the PAT wall. And I don't see why that specific case could not be supported in the compiler in the future. Yes, it would be possible (in this particular case) to workaround it with func printShape<T: Shape>(shape: T). But why would the user have to work through trying to find the correct incantations to please the compiler, when compiler could just figure it out on its own? (EDIT: this exact case is ExistentialSpecializer, as pointed out by @Karl)

1 Like