Why is Function[-A1,...,+B] not about allowing any supertypes as parameters?
Solution 1:
Covariance and contravariance are qualities of the class not qualities of the parameters. (They are qualities that depend on the parameters, but they make statements about the class.)
So, Function1[-A,+B]
means that a function that takes superclasses of A
can be viewed as a subclass of the original function.
Let's see this in practice:
class A
class B extends A
val printB: B => Unit = { b => println("Blah blah") }
val printA: A => Unit = { a => println("Blah blah blah") }
Now suppose you require a function that knows how to print a B
:
def needsB(f: B => Unit, b: B) = f(b)
You could pass in printB
. But you could also pass in printA
, since it also knows how to print B
s (and more!), just as if A => Unit
was a subclass of B => Unit
. This is exactly what contravariance means. It doesn't mean you can pass Option[Double]
into printB
and get anything but a compile-time error!
(Covariance is the other case: M[B] <: M[A]
if B <: A
.)
Solution 2:
This question is old, but I think a clearer explanation is to invoke the Liskov Substitution Principle: everything that's true about a superclass should be true of all its subclasses. You should be able to do with a SubFoo everything that you can do with a Foo, and maybe more.
Suppose we have Calico <: Cat <: Animal, and Husky <: Dog <: Animal. Let's look at a Function[Cat, Dog]
. What statements are true about this? There are two:
(1) You can pass in any Cat (so any subclass of Cat)
(2) You can call any Dog method on the returned value
So does Function[Calico, Dog] <: Function[Cat, Dog]
make sense? No, statements that are true of the superclass are not true of the subclass, namely statement (1). You can't pass in any Cat to a Function that only takes Calico cats.
But does Function[Animal, Dog] <: Function[Cat, Dog]
make sense? Yes, all statements about the superclass are true of the subclass. I can still pass in any Cat -- in fact I can do even more than that, I can pass in any Animal -- and I can call all Dog methods on the returned value.
So A <: B
implies Function[B, _] <: Function[A, _]
Now, does Function[Cat, Husky] <: Function[Cat, Dog]
make sense? Yes, all statements about the superclass are true of the subclass; I can still pass in a Cat, and I can still call all Dog methods on the returned value -- in fact I can do even more than that, I can call all Husky methods on the returned value.
But does Function[Cat, Animal] <: Function[Cat, Dog]
make sense? No, statements that are true of the superclass are not true of the subclass, namely statement (2). I can't call all methods available on Dog on the returned value, only the ones available on Animal.
So with a Function[Animal, Husky]
I can do everything I can do with a Function[Cat, Dog]
: I can pass in any Cat, and I can call all of the Dog methods on the returned value. And I can do even more: I can pass in other animals, and I can call of the methods available on Husky that aren't available on Dog. So it makes sense: Function[Animal, Husky] <: Function[Cat, Dog]
. The first type parameter can be replaced with a superclass, the second with a subclass.
Solution 3:
There are two separate ideas at work here. One is using subtyping to allow more specific arguments to be passed to a function (called subsumption). The other is how to check subtyping on functions themselves.
For type-checking the arguments to a function, you only have to check that the given arguments are subtypes of the declared argument types. The result also has to be a subtype of the declared type. This is where you actually check subtyping.
The contra/co-variance of the parameters & result only factor in when you want to check whether a given function type is a subtype of another function type. So if a parameter has type Function[A1, ... ,B]
, then the argument has to be a function type Function[C1, ..., D]
where A1 <: C1 ...
and D <: B
.
This reasoning isn't specific to Scala and applies to other statically-typed languages with subtyping.