Constrain higher-kinded path-dependent type like a self-type


Disclaimer: This question was posted on stack overflow a few days ago. I’m not a fan of repostings, but I think I have a better chance of finding an answer on a pretty advanced Scala question here than I do on stack overflow.

I am trying to replace a type defined with 2 type parameters and a self-type, with a simpler version based on a single path-dependent type.

My problem is that I can’t express the same constraints. I think a full example will be easier to understand.

class Attribute

// Test 1: Using a 2nd higher-kinded parameter and a self-type
// Advantage: Subclasses are constrained by the self-type to specify the "This" parameter correctly
// Drawback: Container1 has a 2nd type parameter and "AnyContainer1" type is really ugly
sealed trait Container1[+A <: Attribute, +This[+X <: Attribute] <: Container1[X, This]] { self: This[A] => }
class Foo[+A <: Attribute] extends Container1[A, Foo]
//class Bar[+A <: Attribute] extends Container1[A, Foo] // Won't compile :-)

object Container1 {
  // This is ugly...
  type AnyContainer1[+X <: Attribute] = F[X] forSome { type F[+Y <: Attribute] <: Container1[X, F] }
//  type AnyContainer1[+X <: Attribute] = Container1[X, AnyContainer1] // This doesn't compile :-(

// Test 2: Using path-dependent types
// Advantage: Container2 has a single type parameter and AnyContainer2 is easily expressed
// Drawback: The path-dependent parameter can be overridden in a bad way
sealed trait Container2[+A <: Attribute] {
  protected type This[+B <: Attribute] <: Container2[B]
  // Note that I need `This` to take a generic parameter in 
  // order to declare methods like `def foo[T]: This[T]` in subclasses.

class Bar[+A <: Attribute] extends Container2[A] {
  override protected type This[+X <: Attribute] = Bar[X]
class Baz[+A <: Attribute] extends Container2[A] {
  // Will compile even though `Bar != Baz`, I'd like to enforce This = Baz somehow...
  override protected type This[+X <: Attribute] = Bar[X]

object Container2 {
  type AnyContainer2[+X <: Attribute] = Container2[Attribute] // Easy :-)

Is there a better way to constrain the This type while using path-dependent types to keep things clean? With the next version of Scala (Dotty for now), existential types will disappear, and should be replaced with path-dependent ones, so how do I implement this without existential types?


I’d recommend trying to express this as a typeclass instead of using a self-type and F-bounded polymorphism. I don’t know your specific operations so I can’t give you a specific solution, but see @tpolecat 's excellent post for the general strategy.