all after reading Scala covariance / contravariance question and its answer provided by Daniel Spiewak,and Section 19.3-19.7 of book "Programming in Scala", I have another confusion about the definion of Function1[-A, +B]: why its first type parameter is contravariant? I got one reason, it is that this type parameter guarantees that the subtypes always appear earlier than supertypes when there are multiple cases, at the same time, subtype "is a" supertype. for example:
class A
class B extends A
class C extends B
scala> val withDefault: A => B = {
| case x:B => new B
| case x:A => new B }
withDefault: A => B = <function1>
here, (1) case x:B
is earlier than case x:A
, (2) Function1[A,B] <: Function1[B,B]
and there must be other reasons? any message will be appreciated!
Contravariance has nothing to do with pattern matching. Here's why function types are contravariant on their argument types. Consider this function:
def doHigherOrder(handleAnyAnimal: Animal => T,
anyAnimal: Animal ): T = {
// ...foo...
handleAnyAnimal(anyAnimal)
// ...bar...
}
If functions were covariant not contravariant, then a function Duck => T
would also be a subtype of Animal => T
and you could do doHigherOrder(handleAnyDuck)
. This would be an error because then, inside doHigherOrder
, the handleAnyAnimal(anyAnimal)
expression would (at runtime/evaluation) boil down to handleAnyDuck(anyAnimal)
, which is obviously not right because a function that can handle any Duck
probably can't handle any Animal
:
def doHigherOrder(handleAnyDuck: Duck => T,
anyAnimal: Animal ): T = {
// ...foo...
handleAnyDuck(anyAnimal) // <-- ERROR
// ...bar...
}
Moreover, assuming Creature >: Animal
, a function handleAnyCreature: Creature => T
is indeed also a subtype of Animal => T
, because it is OK to pass anyAnimal
into something that can accept anyCreature
.
That's why contravariance is intuitively inevitable with argument types.
However, return values are covariant because they have exactly the same semantics as just values:
val animal = getDuck() // ok
val duck = getAnimal() // <-- ERROR
compare with
val x = handleDuck(animal) // <-- ERROR
val y = handleAnimal(duck) // ok
Figuratively, you assign to function arguments, but from their return values. It becomes more obvious with named arguments:
val x = handle(duck = animal) // <-- ERROR
val y = handle(animal = duck ) // ok
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With