Alan Coromano
Alan Coromano

Reputation: 26008

Using generics in Scala

I wonder, what's the difference between these two:

class SomeClass(val a: Int)

//1
class Class1[T <: SomeClass] {
  def method1(param1: T) = ....
}

//2
class Class2 {
  def method1(param1: SomeClass) = ....
}

Upvotes: 1

Views: 125

Answers (2)

mikołak
mikołak

Reputation: 9705

Functionally? Say why some 3rd-party code has the possibility to load those classes will do the following:

class ThirdPatyClass(val a: Int) extends SomeClass(a) with ThirdPartyTrait

An instance of Class1[ThirdPartyClass] (and a reified corresponding method1) could be used where there would be a type bound of the form

[S <: ThirdPartyTrait]

whereas Class2 couldn't.

And, of course, depending what you do within those methods, you could return an object of type T (or anything that uses T as a bound, e.g. Class[_ <: T]) in Class1.method1.

In general, with Class1 you're giving the compiler (and the developer) a possibility to preserve more information on the type of param1, in contrast with Class2 where you can only preserve the compile-time information that param1 is an instance of SomeClass.

EDIT: to answer the question from the comment - yes, whether to select one approach or the other depends on what you need. If you require more flexibility and more static type information, go with the the generic type bounds. If not, I'd say you should follow YAGNI and not clutter your API.

Upvotes: 3

itsbruce
itsbruce

Reputation: 4843

Class2 is both a class and a type, while Class1 is only a class. There can never be a value with type Class1, although there can be any number of parameterised types (Class1[SomeClass], Class1[SomeSubClass1], Class1[SomeClassType2] etc.), all of which have the same class and will, at run time, only be identifieable as Class1[_] due to type erasure. This has some practical effects:

  1. Type erasure complicates pattern matching.
  2. Type erasure also makes various reflection techniques more difficult to implement for Class1 than Class2.
  3. Subclassing Class1 requires more thought, as variance issues must be considered.

On the other hand, because Class1 is parameterised, it is easy to mix in an arbitrary set of traits and know they all apply safely to the same type, without requiring any common inheritance or special equivalence methods. The compiler will catch incompatible type operations.

trait Trait1[T]
  def method1: T  // Method happens to do something safely compatible with SomeClass
}

trait Trait2[T <: SomeClass]
  def method2: T
}

class NewClass[T <: SomeClass] extends Class1[T] with Trait1[T] with Trait2[T]

Upvotes: 1

Related Questions