Variance
As mentioned earlier, functions are first-class objects in Scala. Scala automatically converts function literals into objects of the FunctionN type
(N = 0 to 22). For example, consider the following anonymous function:
val f: Int => Any = (x: Int) => x
Example 1.45
This function will be converted automatically to the following:
val f = new Function1[Int, Any] {def apply(x: Int) = x}
Example 1.46
Please note that the preceding syntax represents an object of an anonymous class that extends Function1[Int, Any]
and implements its abstract apply
method. In other words, it is equivalent to the following:
class AnonymousClass extends Function1[Int, Any] { def apply(x: Int): Any = x } val f = new AnonymousClass
Example 1.47
If we refer to the type signature of the Function1
trait, we would see the following:
Function1[-T1, +T2]
Example 1.48
T1
represents the argument type and T2
represents the return type. The type variance of T1
is contravariant and that of T2
is covariant. In general, covariance designed by +
means if a class or trait is covariant in its type parameter T
, that is, C[+T]
, then C[T1]
and C[T2]
will adhere to the subtyping relationship between T1
and T2
. For example, since Any
is a supertype of Int
, C[Any]
will be a supertype of C[Int]
.
The order is reversed for contravariance. So, if we have C[-T]
, then C[Int]
will be a supertype of C[Any]
.
Since we have Function1[-T1, +R]
, that would then mean type Function1[Int, Any]
will be a supertype of, say, Function1[Any, String]
.
To see it in action, let’s define a method that takes a function of type Int => Any
and returns Unit
:
def caller(op: Int => Any): Unit = List .tabulate(5)(i => i + 1) .foreach(i => print(s"$i "))
Example 1.49
Let’s now define two functions:
scala> val f1: Int => Any = (x: Int) => x f1: Int => Any = $Lambda$9151/1234201645@34f561c8 scala> val f2 : Any => String = (x: Any) => x.toString f2: Any => String = $Lambda$9152/1734317897@699fe6f6
Example 1.50
A function (or method) with a parameter of type T
can be invoked with an argument that is either of type T
or its subtype. And since Int => Any
is a supertype of Any => String
, we should be able to pass both of these functions as arguments. As can be seen, both of them indeed work:
scala> caller(f1) 1 2 3 4 5 scala> caller(f2) 1 2 3 4 5
Example 1.51