1. Parameterized types
Scala supports parameterized types, which are very similar to generics in Java. Java uses angle brackets (<…>
), while Scala uses square brackets ([…]
), because <
and >
are often used for method names.
code example
val strings: List[String] = List("one", "two", "three")
When you’re trying to understand type signatures in Scaladocs, such as the entry for List
, where you’ll see that the declaration is written as sealed abstract class List[+A]
.
The +
in front of the A
means that List[B]
is a subtype of List[A]
for any B
that is a subtype of A
. This is called covariant typing. It is a reasonably intuitive idea. If we have a function f(list: List[Any])
, it makes sense that passing a List[String]
to it should work fine.
If there is a –
in front of a type parameter, the relationship goes the other way; Foo[B]
would be a supertype of Foo[A]
, if B
is a subtype of A
and the declaration is Foo[-A]
(called contravariant typing).
2. Abstract Types
Scala supports another type abstraction mechanism called abstract types, which can be applied to many of the same design problems for which parameterized types are used. However, while the two mechanisms overlap, they are not redundant. Each has strengths and weaknesses for certain design problems.
These types are declared as members of other types, just like methods and fields. Here is an example that uses an abstract type in a parent class, then makes the type member concrete in child classes:
import java.io._
abstract class BulkReader {
type In
val source: In
def read: String // Read source and return a String
}
class StringBulkReader(val source: String) extends BulkReader {
type In = String
def read: String = source
}
println(new StringBulkReader("Hello Scala!").read)
Just as for parameterized types, if we define the In
type to be String
, the source
field must also be defined as a String
.
Compare them
what’s the advantage here of using type members instead of parameterized types?
The latter are best for the case where the type parameter has no relationship with the parameterized type, like List[A]
when A
is Int
, String
, Person
, etc. A type member works best when it “evolves” in parallel with the enclosing type, as in our BulkReader
example, where the type member needed to match the “behaviors” expressed by the enclosing type. Sometimes this characteristic is called family polymorphism or covariant specialization. (Some confusion about it Q_Q
)
Ref