为什么Function[-A1,...,+B]不允许任何超类型作为参数?

15 浏览
0 Comments

为什么Function[-A1,...,+B]不允许任何超类型作为参数?

我认为可以将协变性(至少对于对象而言)定义为“能够使用较窄(子)类型的值代替某个更宽(超)类型的值的能力”,而逆变性则是完全相反的情况。

显然,Scala函数是Function[-A1,...,+B]的实例,其中逆变参数类型A1等以及协变返回类型B。虽然这对于函数的子类型化很方便,但是根据上述定义,我不应该可以传递任何超类型作为参数吗?

请指出我哪里错了。

0
0 Comments

问题的原因是,函数类型的参数允许更具体的参数类型,这被称为子类型(subsumption)。然而,在检查函数本身的子类型时,函数的参数和结果的逆变与协变并不起作用。因此,如果参数的类型是`Function[A1, ..., B]`,那么参数的类型必须是`Function[C1, ..., D]`,其中`A1 <: C1`,`D <: B`。

为解决这个问题,只需要在函数参数的类型检查中检查给定参数是否是声明参数类型的子类型,并且结果也必须是声明类型的子类型。这是实际进行子类型检查的地方。这个推理不仅适用于Scala,也适用于其他具有子类型的静态类型语言。

0
0 Comments

问题的出现是因为在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原则主题的答案。

0
0 Comments

为什么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的意义所在。

0