Home > Net >  Variance in function types (Scala)
Variance in function types (Scala)

Time:08-24

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)
  • Related