1

I recently had a coworker implement a trait like

 trait CaseClassStuff{
        type T
        val value: T
}

and then used it to instantiate case classes as

case class MyCaseClassString(value: String) extends CaseClassStuff { type T = String }
case class MyCaseClassDouble(value: Double) extends CaseClassStuff { type T = Double }

and I thought that was particularly whacky since it seemed reasonable enough to just do

case class MyCaseClass[T](value: T)

to get the exact same result. There was argument over how using the trait allowed us to avoid needing to update anything using that case class, since with the trait we just explicitly used MyCaseClassString and MyCaseClassDouble in different areas, but I wasn't sure how since they seemed to be ostensibly the same thing, especially since the only change between the two is their type. The program using them was set up to parse out the logic when it was a double or a string received.

So, my question is about whether or not they are different as far as the compiler is concerned, and whether or not there is actual benefit from doing it the way with the trait in general, or if it was just specific to my situation. It wasn't clear to either of us if it was best practice to use the trait or just the type parameter, since it seems like two ways to accomplish the same outcome.

Blake
  • 134
  • 7
  • 1
    They are very different, the first one is a type member used to create path-dependent types, the second one is a type parameter. Unless you have reasons to prefer a path-dependent type, you should prefer just a type parameter since they are more common, simpler, and better supported by the compiler. Additionally, if what you want to do is ensure just a couple of types then create a little ADT with the options without needing to use neither a type parameter nor a type member. – Luis Miguel Mejía Suárez Aug 31 '21 at 19:57
  • 1
    See also https://stackoverflow.com/questions/5581836/why-does-scala-have-path-dependent-types and https://stackoverflow.com/questions/2693067/what-is-meant-by-scalas-path-dependent-types – Gaël J Aug 31 '21 at 20:02
  • 3
    There was actually a time where the two were considered the same. In fact, early designs of what later became Dotty and now is Scala 3 were supposed to get rid of generics altogether, because everything you can do with generics, you can also do with abstract type members. The idea was that `trait T[C]` is just syntactic sugar for an abstract `type` member `C` that is only in scope within the trait itself, exactly like `trait T(f: SomeType)` is syntactic sugar for a value member. However, this line of investigation was later abandoned. – Jörg W Mittag Aug 31 '21 at 21:12

0 Answers0