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 Bs (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.