How to check covariant and contravariant position of an element in the function?

前端 未结 2 784
既然无缘
既然无缘 2020-12-05 12:22

This is a code snippet from one of the articles that I read regarding contravariance and covariance in scala. However, I fail to understand the error message thrown by the s

2条回答
  •  [愿得一人]
    2020-12-05 13:00

    Class Pets is covariant in its type A (because it's marked as +A), but you are using it in a contravariant position. This is because, if you take a look at the Function trait in Scala, you will see that the input parameter type is contravariant, while return type is covariant. Every function is contravariant in its input type and covariant in its return type.

    For example, function taking one argument has this definition:

    trait Function1[-T1, +R]
    

    The thing is, for a function S to be a subtype of function F, it needs to "require (same or) less and provide (same or) more". This is also known as the Liskov substitution principle. In practice, this means that Function trait needs to be contravariant in its input and covariant in its output. By being contravariant in its input, it requires "same or less", because it accepts either T1 or any of its supertypes (here "less" means "supertype" because we are loosening the restriction, e.g. from Fruit to Food). Also, by being covariant in its return type, it requires "same or more", meaning that it can return R or anything more specific than that (here "more" means "subtype" because we are adding more information, e.g. from Fruit to Apple).

    But why? Why not the other way around? Here's an example that will hopefully explain it more intuitively - imagine two concrete functions, one being subtype of another:

    val f: Fruit => Fruit
    val s: Food => Apple
    

    Function s is a valid subtype for function f, because it requires less (we "lose" information going from Fruit to Food) and provides more (we "gain" information going from Fruit to Apple). Note how s has an input type that's a supertype of f's input type (contravariance), and it has a return type that's a subtype of f's return type (covariance). Now let's imagine a piece of code that uses such functions:

    def someMethod(fun: Fruit => Fruit) = // some implementation
    

    Both someMethod(f) and someMethod(s) are valid invocations. Method someMethod uses fun internally to apply fruit to it, and to receive fruit from it. Since s is a subtype of f, that means we can supply Food => Apple to serve as a perfectly good instance of fun. Code inside someMethod will at some point feed fun with some fruit, which is OK, because fun takes food, and fruit is food. On the other hand, fun having Apple as return type is also fine, because fun should return fruit, and by returning apples it complies with that contract.

    I hope I managed to clarify it a bit, feel free to ask further questions.

提交回复
热议问题