17. Type Parameters - RobertMakyla/scalaWiki GitHub Wiki
KEY POINTS:
- Classes, traits, methods, and functions can have type parameters.
- Place the type parameters after the name, enclosed in square brackets.
- Type bounds have the form T <: UpperBound, T >: LowerBound, T <% ViewBound, T : ContextBound.
- You can restrict a method with a type constraint such as (implicit ev: T <:< UpperBound).
- Use +T (covariance) to indicate that a generic type's subtype relationship is in the same direction... as the parameter T, or -T (contravariance) to indicate the reverse direction.
- Covariance is appropriate for parameters that denote outputs, such as elements in an immutable collection.
- Contravariance is appropriate for parameters that denote inputs, such as function arguments.
class Pair[T, S](val first: T, val second: S)
val p1 = new Pair(42, "String") // It's a Pair[Int, String]
def getMiddle[T](a: Array[T]) = a(a.length / 2)
getMiddle( Array("a","b","c") ) // --> "b"
val f = getMiddle[String] _ // The function (with T as String), saved in f
Bound
class Pair[T <: Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
val p = new Pair("aaa","bbb") ; p.smaller // --> "aaa"
Note: The S <: T means that S is subtype of T (upper bound)
Note: The S >: T means that S is supertype of T (lower bound)
View Bound (Bounds for implicit conversions)
Our example: class Pair[T <: Comparable[T]]
cannot work for Pair(1,2) cause Int doesn't extend Comparable[Int]
However Int is implicitly converted to RichInt, and RichInt extends Comparable[RichInt]
class Pair[T <% Comparable[T]] // this will work
The <% means that T can be converted with implicit conversion to sth that extends Comparable
Summing up: A bound T <: V means that T extends V
Summing up: A view bound T <% V means that T has an implicit conversion which extends V
val ord = Ordering[String] // gives implicit value: scala.math.Ordering[String]
ord.compare("a", "b") // --> -1
A context bound 'T : M' requires that there is implicit value of M[T] (chapter 21)
A context bound 'T : Ordering' requires that there is implicit value of Ordering[T]
'String' would do a good 'T' cause Ordering[String] gives implicit value
class Pair[T : Ordering](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]) =
if (ord.compare(first, second) < 0) first else second
}
new Pair[Int](333, 11) smaller // -> 11
new Pair[String]("a", "z") smaller // -> a
//or we can let the compile figure out the 'T' type by the parameters
new Pair(333, 11) smaller // -> 11
new Pair("a", "z") smaller // -> a
Summing up: Context bound 'String : Ordering' requires that Ordering[String] gives implicit value
i.e. an object which is ready to use
The 'Manifest' Context Bound - To create collections of generic types
To instantiate a generic Array[T], We need a Manifest[T] object first.
def makePair[T : Manifest](first: T, second: T) = {
Array[T](first,second)
}
I couldn't just write: def makePair[T](first: T, second: T)
, because if I write generic function which constructs generic array, I need to help it out
, I need to pass Manifest object.
Multiple Bounds
T <: Upper >: Lower // upper and lower bounds
T <: Comparable[T] with Serializable with Cloneable // bounds with traits
T <% Comparable[T] <% String // 2x view bound
T : Ordering : Manifest // 2x context bound
Type Constraints
T =:= U // test if T equals U
T <:< U // test if T is subtype of U
T <%< U // test if T is view convertible of U
// (if T implicitly converts to something that extends U)
I cannot write:
def firstLast[A , C <: Iterable[A]](it: C) = (it.head, it.last)
because type recogniser tries to recognize A and C in one step.
It tries to use A in Iterable[A], before A is registered as generic type
So we need to split it into 2 steps
def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.last)
firstLast( Iterable(1,2,3) ) // --> (Int, Int) = (1,3)
Variance
def makeFriends(p: MyPair[Person]) = {}
Calling it with parameter MyPair[Student] gives error even if Student extends Person
To be able to do that, I need to declare it in MyPair definition:
class MyPair[+T](val first: T, val second: T)
It means that MyPair[Student] is a subclass of MyPair[Person]
So, we can use MyPair[SUB_T] whenever we expect MyPair[T]
class MyPair[-T](val first: T, val second: T)
It means that MyPair[SUB_T] is a SUPERCLASS of MyPair[T]
It means that MyPair[Student] is a SUPERCLASS of MyPair[Person]
(other direction than Student-Person relation)
class MyPair[+T](var first: T, var second: T) // Error when params are variables
cause while assigning new var, i can mess with var type, so I mess with type of MyPair[T]
Note: class C[+T] {} means that C[SUB_T] <: C[T] (normal direction like in SUB_T - T relation)
Note: class C[-T] {} means that C[SUB_T] >: C[T] (other direction than SUB_T - T relation)
Note: class C[ T] {} means that neither C[SUB_T] or C[T] are subtype of the other
Objects Can't Be Generic
object ObjPair[T] extends MyPair[T] // Error
workaround:
object ObjPair extends MyPair[Nothing] // OK, Nothing is a subtype of everything
Wildcards (alternative to Variance)
void makeFriends(List<? extends Person> people) // This is Java
In Scala, you don't need the wildcard for a covariant MyPair class - thanks to variance usage.
(covariant MyPair means that MyPair[SUB_T] is subtype of MyPair[T] )
class MyPair[T+](var first: T, var second: T)
def makeFriends(p: MyPair[Person]) ={} // OK to call with a MyPair[Student]
But suppose MyPair is already invariant (MyPair[SUB_T] is not subtype of MyPair[T] ) and we can't change it.
Then we can use Wildcards lust like in Java:
class MyPair[T](var first: T, var second: T)
def makeFriends(p: MyPair[_ <: Person]) ={} // OK to call with a MyPair[Student]