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.

Generic Classes

class Pair[T, S](val first: T, val second: S)

val p1 = new Pair(42, "String") // It's a Pair[Int, String]

Generic Functions

     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

Context Bounds (Bounds for implicit values)

     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]
⚠️ **GitHub.com Fallback** ⚠️