Copyright © Cay S. Horstmann 2015
This work is licensed under a Creative Commons Attribution 4.0 International License
Given classes
class Pair[T](val first: T, val second: T) class Person(val name: String) class Student(name: String, val major: String) extends Person(name)and a method
def makeFriends(p: Pair[Person]): Unit = { ... }which of the following can be passed to
makeFriends
?
Pair[Person]
Pair[Person]
and a Pair[Student]
Pair[Person]
and a Pair[Any]
Pair[...]
G
is a generic type, there is no relationship between G[S]
and G[T]
, no matter what the relationship between S
and T
may be.class Pair[+T](val first: T, val second: T)
Pair[Student] <: Pair[Person]
Seq[+A]
)A Friend[T]
is willing to befriend anyone of type T
.
trait Friend[T] { def befriend(someone: T) }
Suppose you have a function
def makeFriendWith(s: Student, f: Friend[Student]): Unit = { f.befriend(s) }
What should you be able to pass in for f
?
Friend[Student]
Friend[Person]
and a Friend[Student]
Friend[GraduateStudent]
and a Friend[Student]
Friend[...]
G
is contravariant if
S <: T => G[T] <: G[S]
Student <: Person => Friend[Person] <: Friend[Student]
-
annotation:
trait Friend[-T] { def befriend(someone: T) }
trait Function1[-T, +R]
def friends(students: Vector[Student], find: Function1[Student, Person])with
def findStudent(p: Person) : Student
Student
objectsStudent
objects, which are Person
objectsArray[Student]
to an Array[Person]
?
val students = Array(student1, student2)
val people = students // Not legal, but suppose it was...
people(0) = new Person("Fred")
Now students(0)
is a Person
, not a Student
!class Pair[+T](var first: T, var second: T)
T
occurs in contravariant position in first_=(value: T)
”foldLeft[B](z: B)(op: (A, B) => B): B - + + - +
A
can be +
, but B
must be invariantclass Pair[+T](val first: T, val second: T) { def replaceFirst(newFirst: T) = new Pair[T](newFirst, second) }
newFirst: T
is in contravariant positiondef replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
R
is invariant and can occur in a covariant positionabstract class List[+T] case class ::[T](val head: T, tail: List[T]) extends List[T] case object Nil extends List[Nothing]
Nothing
is a subtype of all typesList[Nothing]
is a subtype of all List[T]
val lst = new ::(42, Nil)
::[Int]
. List[Int]
List[Nothing] <: List[Int]
? extends T, ? super T
public class Collections { // This is Java
public static <T> void copy(List<? super T> dest, List<? extends T> src) { ... }
...
}
Comparator
as contravariant—must provide variance with every use
public class Collections { // This is Java
public static <T> void sort(List<T> list, Comparator<? super T>> c) { ... }
...
}
def copy[T](dest: Array[_ >: T], src: Array[_ <: T])
count
, groupBy
, and aggregate
methods of the Iterable[+A]
trait. Why is it in a covariant position in these methods?
replaceFirst
method has a type bound. Why can’t you define an equivalent
method on a mutable Pair[T]
?
def replaceFirst[R >: T](newFirst: R) { first = newFirst } // Error
replaceFirst
method for an immutable Pair[+T]
. But there is a reason. Suppose you could define
def replaceFirst(newFirst: T) = new Pair(newFirst, second)Then you could also define
class NastyDoublePair(x: Double, y: Double) extends Pair[Double](x, y) { override def replaceFirst(newFirst: Double) = new Pair(math.sqrt(newFirst), second) }Now what would happen with this call?
val p: Pair[Any] = new NastyDoublePair(3, 4) val q = p.replaceFirst("Hello")Is the first line valid? That is, can you assign a
NastyDoublePair
to a Pair[Any]
? Pair[Any].replaceFirst
with a "String"
(assuming that it is defined as above?)class MyMap[K, V](entries: List[(K, V)]) { def this() { this(Nil) } def get(key: K) = entries.find(_._1 == key) match { case Some(e) => Some(e._2) case None => None } def put(key: K, value: V) = new MyMap((key, value) :: entries) }Make an instance as
val mm = new MyMap[String, Int].put("Fred", 1).put("Wilma", 10).put("Fred", 5)What do you get for
mm.get("Fred") mm.get("Barney")
def total(students: List[Student], scores: MyMap[Student, Int]) = students.map(scores.get(_).getOrElse(0)).sumShould it be ok to pass it a
MyMap[Person, Int]
?MyMap
to make that work. What did you do?def findById(ids: List[Int], people: MyMap[Int, Person]) = ids.flatMap(people.get(_))Should it be ok to pass it a
MyMap[Int, Student]
?scala.collection.immutable.Map[A, +B]
contravariant in A
?class Person(val name: String) extends Ordered[Person] { def compare(that: Person) = name.compareTo(that.name) } class Student(name: String, val major: String) extends Person(name)Make a
Person
and a Student
object. Can you call person.compare(student)
? student.compare(person)
?Vector[Person]
, fill it with some people, and call people.max
. What happens.Vector[Student]
. What happens?Ordered
in Scaladoc.)Ordered
to be a generic trait with a single abstract method compare(that: T) : Int
. Make it contravariant so that a Ordered[Person]
is also a Ordered[Student]
. Define max[T <: Ordered[T]](s: Seq[T]) = s.reduce(...)
. What is max
?max(people)
? max(students)
? Ordered
contravariant? It's been discussed at great length here. Just have a glance at it and admire the complexity and the emotions :-)Ordered
, or make T
invariant. The call max(students)
should no longer compile. In Java, you'd do this:
<T extends Comparable<? super T>> max(Collection<T> coll)or in Scala notation
def max[T <: Ordered[_ >: T]]Why does this solve the problem?
type SuperOrdered[T] = Ordered[_ >: T] def max[T <: SuperOrdered[T]]Now what happens?
Do this as individual work, not with your partner
When all done, email the signed zip files to Fatemeh.Borran@heig-vd.ch