So I'm trying to understand variance in Scala. I know that if A1 >: A2 and B1 <: B2, then (A1 => B1) <: (A2 => B2), in other words functions are contravariant in the domain and covariant in the domain. In this example, the first two cases respects this rule but I don't understand why CASE3 is correct, because the two functions are covariant in the domain (Cat <: Animal) so it should not be allowed. Moreover, inside the function fun2 I pass an object of type Dog as input to b, which shouldn't be accepted because the function I'm passing should only get Cat as input. Please help me understand this topic better.
class Animal{}
class Cat extends Animal{}
class Persian extends Cat{}
class Dog extends Animal{}
def fun(a:Cat => Animal): Unit = {
a(new Persian)
}
def fun2(b: Animal => Cat): Unit = {
b(new Dog)
}
fun(Animal => new Cat) //CASE1: OK: contrarvariant in domain, covariant in codomain
fun2(Cat => new Animal) //CASE2: NO: covariant in domain, contravariant in codomain
fun2(Cat => new Cat) //CASE3: OK but I don't know why it works because it's covariant in the domain
CodePudding user response:
I don't understand why CASE3 is correct.
It's not. And it won't work when you will try to pass an object of type Cat => Cat
. Right now you are not passing anything. Your syntax is not correct. For example, either use eta-expansion, or try to pass a function:
def myMethod(x: Cat): Cat = new Cat
fun2(myMethod) // type mismatch; found: Cat => Cat; required: Animal => Cat
val f: Cat => Cat = x => x // the identity function
fun2(f) // type mismatch; found: Cat => Cat; required: Animal => Cat
This also answers your other question.
CodePudding user response:
Just to clarify exactly what's happening here. Alin's answer is correct, I just want to emphasize what your current code is doing. Our two functions look like this.
def fun(a: Cat => Animal): Unit
def fun2(b: Animal => Cat): Unit
Now your first call
fun(Animal => new Cat)
Animal
is not the name of the type, it's the name of a local variable. So the function is one that takes a single argument (called Animal
), ignores it, and returns a Cat
. fun
expects a Cat => Animal
, so the inferred type of Animal
will, incidentally, be Cat
. What you probably meant to write was
fun((_: Animal) => new Cat)
Now the second one.
fun2(Cat => new Animal)
Same problem. Cat
is a local variable whose name happens to be Cat
. Since fun2
wants an Animal => Cat
, the inferred type of Cat
is Animal
(which is fine; since we never used the variable, the compiler is free to infer whatever type is pleases). However, the return type must be Animal
, which fails because, as you've correctly pointed out, functions are covariant in the result type. Again, to get the argument behavior you wanted, you probably meant
fun2((_: Cat) => new Animal)
Finally, the third example.
fun2(Cat => new Cat)
This one tripped me up for a minute, but it's the same principle. Cat
is a function argument that's unused. It might look like it's used on the right-hand side, but it's not. The word after new
must be a type name, not a local variable, so the left-hand-side Cat
and the right-hand-side Cat
have no relation to each other. Since fun2
expects an Animal => Cat
, the inferred type of Cat
(the variable on the left) is Animal
, and the function returns a Cat
as expected. If you write the function to take an explicit argument of type Cat
, it will fail as you would expect.
fun2((_: Cat) => new Cat)