0

I have a question on when to use type parameterization vs base class type when defining a method/class in Scala especially when the types allowed are in same hierarchy - constrained using type bound.

for eg:

trait TypeA
case class SubTypeA1(in: String) extends TypeA
case class SubTypeA2(in: String) extends TypeA

case class NewType[T <: TypeA](inTypeA: T) 
case class NewTypeV2(inTypeA: TypeA) 

def testMethod1(in: String): NewType[TypeA] = NewType(SubTypeA1(in))
def testMethod2(in: NewType[TypeA]): Unit = print("Some functionality")
def testMethod3(in: String): NewTypeV2 = NewTypeV2(SubTypeA1(in))
def testMethod4(in: NewTypeV2): Unit = print("Some functionality")

In the above case/in general when constraining allowed types to a certain upper bound, what advantage would I get with NewType over NewTypeV2 or vice versa? They both look equivalent to me.

As I understand it, if I were to add some implicit condition check like NewType[T: Addable] thats present across types of different hierarchies then type parameterization would make sense, besides that what are the reasons I should prefer type parameterization over using Interface type or Base class type like the type of inTypeA member of NewTypeV2 case class.

Is defining type parameterized way like NewType[T] considered more "Functional" style over the other?

And Secondly, question on Variance vs Type bound. In the above code block NewType is Invariant on Type T, so NewType[SubTypeA1] is not a subtype of NewType[TypeA], they are unrelated, correct?

If my understanding on type invariance is correct as mentioned above, how is the testMethod1 compiling? As I am explicitly passing SubTypeA1 but still it gets casted to NewType[TypeA] and also it can be passed to testMethod2 without issue. What am I misunderstanding here?

scala> testMethod1("test")
res0: NewType[TypeA] = NewType(SubTypeA1(test))

scala> testMethod2(testMethod1("test"))
Some functionality

scala> NewType(SubTypeA1("tst"))
res3: NewType[SubTypeA1] = NewType(SubTypeA1(tst))
user2864740
  • 60,010
  • 15
  • 145
  • 220
Aandal
  • 51
  • 2
  • 11
  • For my question, In addition to Tim's answer below, I found the following helpful posts helpful as well. But they are not specifically for Scala. https://softwareengineering.stackexchange.com/questions/276433/what-is-the-reason-of-using-an-interface-versus-a-generically-constrained-type and https://softwareengineering.stackexchange.com/questions/303289/generics-vs-common-interface – Aandal Sep 20 '20 at 09:05

1 Answers1

1
case class NewType[T <: TypeA](inTypeA: T) 
case class NewTypeV2(inTypeA: TypeA) 

The difference between these two is the type of inTypeA:

In NewType the type of inTypeA is the type parameter T which can be any subtype of TypeA. The compiler knows what the actual subtype is.

In NewTypeV2 the type of inTypeA is TypeA. The value will be a subtype of TypeA, but all the compiler knows is that it is a TypeA

This matters if you have a method that requires a particular subtype of TypeA:

def subtypeMethod(in: SubTypeA1) = ???

val a1 = SubTypeA1("string")

val n = NewType(a1)
subtypeMethod(n.inTypeA) // Yes, inTypeA is type SubTypeA1

val n2 = NewTypeV2(a1)
subtypeMethod(n2.inTypeA) //No, inTypeA is type TypeA
Tim
  • 26,753
  • 2
  • 16
  • 29
  • Thanks for the explanation. So in general, type constrained generic parameter gives more type safety than using interface type? Also, Could you please help with the second question regarding variance? or should I post that as a separate question? – Aandal Sep 16 '20 at 10:02