Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usefulness of explicitly-typed self references in the Cake Pattern

It seems that the most common use of Scala's explicitly-typed self references is in the "Cake pattern," in which a module's dependencies are declared like:

class Foo { this: A with B with C =>
  // ...
}

In general, ignoring the cake pattern for a moment, A, B and C can refer to any type-level thing, such as a type parameter:

class Outer[A, B, C] {
  class Inner { this: A with B with C =>
    // ...
  }
}

... or an abstract type member:

class Outer {
  type A
  type B
  type C
  class Inner { this: A with B with C =>
    // ...
  }
}

In neither of these cases could we have written abstract class Inner extends A with B with C, because A, B and C are not known to be traits. The explicitly-typed self references are necessary here. However, I've only ever seen the cake pattern done with traits:

trait A { def a }
trait B { def b }
trait C { def c }
class Foo { this: A with B with C =>
  // ...
}

In this case, we can instead write abstract class Foo extends A with B with C directly, which if I'm not mistaken has the same meaning. Am I correct? If not, then how do they differ; and if so, why does everybody seem to use the explicitly-typed self reference regardless?

like image 265
mergeconflict Avatar asked Jul 13 '12 00:07

mergeconflict


2 Answers

It seems like there are two main differences I overlooked:

  1. Although both the explicit self-type annotation and the simple extends keyword describe an "is-a" relationship between two types, that relationship is not externally visible in the former case:

    scala> trait T
    defined trait T
    
    scala> class C { this: T => }
    defined class C
    
    scala> implicitly[C <:< T]
    <console>:10: error: Cannot prove that C <:< T.
    

    This is a good thing, because in the cake pattern you don't want your "module" object to be inadvertently, polymorphically used as one of the traits on which it depends.

  2. As noted explicitly by Mushtaq and indirectly by Daniel, dependencies can be cyclic when using self-type annotations. Cyclic dependencies are very common, and not necessarily bad (assuming the interdependent components don't require each other for initialization, or that we can somehow tie the knot between them), so this is another clear benefit of self-type annotations over inheritance.

like image 92
mergeconflict Avatar answered Oct 08 '22 21:10

mergeconflict


When you use inheritance, you make decisions about initialization order. When you use self types, you leave that open.

There are other differences, but I think most of them are implementation details and may disappear. I know some of them are.

like image 26
Daniel C. Sobral Avatar answered Oct 08 '22 23:10

Daniel C. Sobral