Scala 类型下界错误?
Scala type lowerbound bug?
case class Level[B](b: B){
def printCovariant[A<:B](a: A): Unit = println(a)
def printInvariant(b: B): Unit = println(b)
def printContravariant[C>:B](c: C): Unit = println(c)
}
class First
class Second extends First
class Third extends Second
//First >: Second >: Third
object Test extends App {
val second = Level(new Second) //set B as Second
//second.printCovariant(new First) //error and reasonable
second.printCovariant(new Second)
second.printCovariant(new Third)
//second.printInvariant(new First) //error and reasonable
second.printInvariant(new Second)
second.printInvariant(new Third) //why no error?
second.printContravariant(new First)
second.printContravariant(new Second)
second.printContravariant(new Third) //why no error?
}
似乎 scala 的下界类型检查有错误...对于不变大小写和逆变大小写。
不知道上面的代码有没有bug
永远记住,如果 Third
扩展了 Second
,那么每当需要 Second
时,都可以提供 Third
。这称为子类型多态性。
考虑到这一点,second.printInvariant(new Third)
编译就很自然了。您提供了一个 Third
,它是 Second
的子类型,所以它会被检出。这就像为采用 Fruit 的方法提供 Apple。
这意味着你的方法
def printCovariant[A<:B](a: A): Unit = println(a)
可以写成:
def printCovariant(a: B): Unit = println(a)
不丢失任何信息。由于子类型多态性,第二个接受B及其所有subclasses,与第一个相同
您的第二个错误案例也是如此 - 这是子类型多态性的另一种情况。您可以传递新的 Third,因为 Third 实际上是 Second(请注意,我使用的是 subclass 和 superclass 之间的“is-a”关系,取自面向对象的表示法)。
如果您想知道为什么我们甚至需要上限(子类型多态性还不够吗?),请观察这个例子:
def foo1[A <: AnyRef](xs: A) = xs
def foo2(xs: AnyRef) = xs
val res1 = foo1("something") // res1 is a String
val res2 = foo2("something") // res2 is an Anyref
现在我们确实观察到了差异。尽管子类型多态性允许我们在两种情况下都传入一个字符串,但只有方法 foo1
可以引用其参数的类型(在我们的例子中是一个字符串)。方法 foo2
会很乐意接受一个字符串,但不会真正知道它是一个字符串。因此,当您想保留类型时,上限会派上用场(在您的情况下,您只是打印出值,所以您并不真正关心类型 - 所有类型都有一个 toString 方法)。
编辑:
(额外的细节,你可能已经知道了,但为了完整起见我会把它放上去)
上限的用途比我在这里描述的要多,但在参数化方法时,这是最常见的情况。当参数化一个class时,那么你可以用上界描述协方差,用下界描述逆变。例如,
class SomeClass[U] {
def someMethod(foo: Foo[_ <: U]) = ???
}
表示方法 someMethod
的参数 foo
在其类型上是协变的。怎么样?好吧,通常(也就是说,没有调整方差),子类型多态性不允许我们传递一个 Foo
参数化的类型参数的子类型。如果 T <: U
,那并不意味着 Foo[T] <: Foo[U]
。我们说 Foo
在它的类型上是不变的。但是我们只是调整了方法以接受使用 U
或 其任何子类型 参数化的 Foo
。现在这是有效的协方差。所以,就 someMethod
而言——如果某种类型 T
是 U
的子类型,那么 Foo[T]
就是 Foo[U]
的子类型。太好了,我们实现了协方差。但请注意,我说的是 "as long as someMethod
is concerned"。 Foo
在此方法中其类型是协变的,但在其他方法中它可能是不变的或逆变的。
这种variance declaration叫做use-site variance 因为我们在使用的时候声明一个类型的variance(这里是作为方法参数类型的someMethod
)。这是 Java 中唯一的一种方差声明。当使用 use-site variance 时,你要注意 get-put principle (google it)。基本上这个原则说我们只能从协变 classes 中得到东西(我们不能放),反之亦然对于逆变 classes(我们可以放但不能得到)。在我们的例子中,我们可以这样演示:
class Foo[T] { def put(t: T): Unit = println("I put some T") }
def someMethod(foo: Foo[_ <: String]) = foo.put("asd") // won't compile
def someMethod2(foo: Foo[_ >: String]) = foo.put("asd")
更一般地说,我们只能将协变类型用作 return 类型,将逆变类型用作参数类型。
现在,use-site declaration 很好,但在 Scala 中更常见的是利用 declaration-site variance(Java 没有).这意味着我们将在定义 Foo
时描述 Foo
的泛型类型的变化。我们会简单地说 class Foo[+T]
。现在我们不需要在编写使用 Foo
的方法时使用边界;我们宣称 Foo
在每个用例和每个场景中其类型都是永久协变的。
有关 Scala 中方差的更多详细信息,请随时查看我关于此主题的 blog post。
case class Level[B](b: B){
def printCovariant[A<:B](a: A): Unit = println(a)
def printInvariant(b: B): Unit = println(b)
def printContravariant[C>:B](c: C): Unit = println(c)
}
class First
class Second extends First
class Third extends Second
//First >: Second >: Third
object Test extends App {
val second = Level(new Second) //set B as Second
//second.printCovariant(new First) //error and reasonable
second.printCovariant(new Second)
second.printCovariant(new Third)
//second.printInvariant(new First) //error and reasonable
second.printInvariant(new Second)
second.printInvariant(new Third) //why no error?
second.printContravariant(new First)
second.printContravariant(new Second)
second.printContravariant(new Third) //why no error?
}
似乎 scala 的下界类型检查有错误...对于不变大小写和逆变大小写。
不知道上面的代码有没有bug
永远记住,如果 Third
扩展了 Second
,那么每当需要 Second
时,都可以提供 Third
。这称为子类型多态性。
考虑到这一点,second.printInvariant(new Third)
编译就很自然了。您提供了一个 Third
,它是 Second
的子类型,所以它会被检出。这就像为采用 Fruit 的方法提供 Apple。
这意味着你的方法
def printCovariant[A<:B](a: A): Unit = println(a)
可以写成:
def printCovariant(a: B): Unit = println(a)
不丢失任何信息。由于子类型多态性,第二个接受B及其所有subclasses,与第一个相同
您的第二个错误案例也是如此 - 这是子类型多态性的另一种情况。您可以传递新的 Third,因为 Third 实际上是 Second(请注意,我使用的是 subclass 和 superclass 之间的“is-a”关系,取自面向对象的表示法)。
如果您想知道为什么我们甚至需要上限(子类型多态性还不够吗?),请观察这个例子:
def foo1[A <: AnyRef](xs: A) = xs
def foo2(xs: AnyRef) = xs
val res1 = foo1("something") // res1 is a String
val res2 = foo2("something") // res2 is an Anyref
现在我们确实观察到了差异。尽管子类型多态性允许我们在两种情况下都传入一个字符串,但只有方法 foo1
可以引用其参数的类型(在我们的例子中是一个字符串)。方法 foo2
会很乐意接受一个字符串,但不会真正知道它是一个字符串。因此,当您想保留类型时,上限会派上用场(在您的情况下,您只是打印出值,所以您并不真正关心类型 - 所有类型都有一个 toString 方法)。
编辑:
(额外的细节,你可能已经知道了,但为了完整起见我会把它放上去)
上限的用途比我在这里描述的要多,但在参数化方法时,这是最常见的情况。当参数化一个class时,那么你可以用上界描述协方差,用下界描述逆变。例如,
class SomeClass[U] {
def someMethod(foo: Foo[_ <: U]) = ???
}
表示方法 someMethod
的参数 foo
在其类型上是协变的。怎么样?好吧,通常(也就是说,没有调整方差),子类型多态性不允许我们传递一个 Foo
参数化的类型参数的子类型。如果 T <: U
,那并不意味着 Foo[T] <: Foo[U]
。我们说 Foo
在它的类型上是不变的。但是我们只是调整了方法以接受使用 U
或 其任何子类型 参数化的 Foo
。现在这是有效的协方差。所以,就 someMethod
而言——如果某种类型 T
是 U
的子类型,那么 Foo[T]
就是 Foo[U]
的子类型。太好了,我们实现了协方差。但请注意,我说的是 "as long as someMethod
is concerned"。 Foo
在此方法中其类型是协变的,但在其他方法中它可能是不变的或逆变的。
这种variance declaration叫做use-site variance 因为我们在使用的时候声明一个类型的variance(这里是作为方法参数类型的someMethod
)。这是 Java 中唯一的一种方差声明。当使用 use-site variance 时,你要注意 get-put principle (google it)。基本上这个原则说我们只能从协变 classes 中得到东西(我们不能放),反之亦然对于逆变 classes(我们可以放但不能得到)。在我们的例子中,我们可以这样演示:
class Foo[T] { def put(t: T): Unit = println("I put some T") }
def someMethod(foo: Foo[_ <: String]) = foo.put("asd") // won't compile
def someMethod2(foo: Foo[_ >: String]) = foo.put("asd")
更一般地说,我们只能将协变类型用作 return 类型,将逆变类型用作参数类型。
现在,use-site declaration 很好,但在 Scala 中更常见的是利用 declaration-site variance(Java 没有).这意味着我们将在定义 Foo
时描述 Foo
的泛型类型的变化。我们会简单地说 class Foo[+T]
。现在我们不需要在编写使用 Foo
的方法时使用边界;我们宣称 Foo
在每个用例和每个场景中其类型都是永久协变的。
有关 Scala 中方差的更多详细信息,请随时查看我关于此主题的 blog post。