Why is Function[-A1,…,+B] not about allowing any supertypes as parameters?

前端 未结 5 1464
别那么骄傲
别那么骄傲 2020-12-01 00:00

I believe one can define covariance (at least, for objects) as \'the ability to use a value of a narrower (sub) type in place of a value of some wider (super) type\', and th

相关标签:
5条回答
  • 2020-12-01 00:05

    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.

    0 讨论(0)
  • 2020-12-01 00:08

    Covariant means converting from wider (super) to narrower (sub). For example, we have two class: one is animal (super) and the other one is cat then using covariant, we can convert animal to cat.

    Contra-variant is just the opposite of covariant, which means cat to animal.

    Invariant means it's unable to convert.

    0 讨论(0)
  • 2020-12-01 00:11

    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.)

    0 讨论(0)
  • 2020-12-01 00:20

    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.

    0 讨论(0)
  • 2020-12-01 00:30

    A simplified explanation

    class A
    class B extends A
    val printA: A => Unit = { a => println("Blah blah blah") }
    printA(new A())   //"Blah blah blah"
    printA(new B())   //"Blah blah blah"
    

    contravariance rule:

    If B is a subtype of A, then printA[A] is a subtype of printA[B]

    Since printA[B] is the superclass, we can use printA(new B())

    0 讨论(0)
提交回复
热议问题