为什么Function[-A1,...,+B]不允许任何超类型作为参数?
问题的出现是因为在Function[-A1,...,+B]中,参数不允许使用任何超类型。根据里氏替换原则,对于一个超类而言,所有的子类都应该具有相同的特性。也就是说,对于一个SubFoo,你应该能够做任何你可以对Foo做的事情,甚至更多。
假设我们有Calico <: Cat <: Animal,Husky <: Dog <: Animal。现在考虑一个Function[Cat, Dog]。关于这个函数有两个真实的陈述:
(1) 你可以传入任何Cat(也就是Cat的子类)
(2) 你可以在返回的值上调用任何Dog的方法
那么Function[Calico, Dog] <: Function[Cat, Dog]是否有意义呢?不,超类的陈述在子类中不成立,特别是陈述(1)。你不能将任何Cat传递给只接受Calico cat的函数。
但是Function[Animal, Dog] <: Function[Cat, Dog]是否有意义呢?是的,超类的所有陈述在子类中都成立。我仍然可以传入任何Cat,事实上我可以做更多的事情,我可以传入任何Animal,而且我可以在返回的值上调用所有Dog的方法。
所以A <: B意味着Function[B, _] <: Function[A, _]。
现在,Function[Cat, Husky] <: Function[Cat, Dog]是否有意义呢?是的,超类的所有陈述在子类中都成立;我仍然可以传入一个Cat,并且我仍然可以在返回的值上调用所有Dog的方法,事实上我可以做更多的事情,我可以调用返回值上所有Husky的方法。
但是Function[Cat, Animal] <: Function[Cat, Dog]是否有意义呢?不,超类的陈述在子类中不成立,特别是陈述(2)。我不能在返回值上调用所有Dog上可用的方法,只能调用在Animal上可用的方法。
所以使用Function[Animal, Husky],我可以做所有我可以用Function[Cat, Dog]做的事情:我可以传入任何Cat,并且我可以调用返回值上所有Dog的方法。而且我可以做更多的事情:我可以传入其他动物,并且我可以调用Husky上可用而Dog上不可用的所有方法。所以Function[Animal, Husky] <: Function[Cat, Dog]是有意义的。第一个类型参数可以被一个超类替换,第二个类型参数可以被一个子类替换。
如何用程序检查<:关系呢?可以创建一个方法,接受一个Function[Cat, Dog]作为参数。尝试传入一个Function[Cat, Animal],你将会得到一个编译错误。尝试传入一个Function[Calico, Animal],你也会得到一个编译错误。它们不是Function[Cat, Dog]的子类。然后尝试传入一个Function[Animal, Dog],这将正常工作。再尝试传入一个Function[Cat, Husky],这也将正常工作。它们都是Function[Cat, Dog]的子类。
非常好的解释!可以被视为variance和Liskov原则主题的答案。
为什么Function[-A1,...,+B]不允许任何超类型作为参数?
函数的协变和逆变是类的属性,而不是参数的属性。它们是依赖于参数的属性,但是它们是关于类的陈述。
所以,Function1[-A,+B]意味着一个接受A的超类的函数可以看作是原始函数的子类。
让我们看一个实际的例子:
class A
class B extends A
val printB: B => Unit = { b => println("Blah blah") }
val printA: A => Unit = { a => println("Blah blah blah") }
现在假设你需要一个函数,它知道如何打印一个B:
def needsB(f: B => Unit, b: B) = f(b)
你可以传递printB。但是你也可以传递printA,因为它也知道如何打印B(还有更多!),就好像A => Unit是B => Unit的子类一样。这正是逆变的意思。它并不意味着你可以将Option[Double]传递给printB并得到其他结果而不是编译时错误!
(协变是另一种情况:如果B <: A,则M[B] <: M[A]。)
谢谢,非常清楚。试图(重新)定义:“协变和逆变是指导类型之间的子类型关系的属性,取决于其组件类型之间的相同关系的本质”。抽象,我知道,但我更喜欢一个没有示例的定义(尽管你的示例非常有帮助)。
谢谢-A,你能解释一下为什么在Function1[-A,+B]中是+B吗?
通过例子解释:如果你返回一个String,你肯定返回一个Object,所以... => String必须是... => Object的子类。这就是+B的意义所在。