Scala 下限的行为不像我预期的那样



今天我花了几个小时来理解 Scala 中下限背后的逻辑,但我读得越多,它就越混乱。您能对此进行一些说明吗?

以下是我们研讨会的简单班级层次结构:

class Animal
class Pet extends Animal
class Wild extends Animal
class Dog extends Pet
class Cat extends Pet
class Lion extends Wild
class Tiger extends Wild

因此,层次结构如下所示:

Animal
/    
Pet    Wild
/     /  
Dog Cat Lion Tiger

下面是客户端代码:

object Main extends App {
//I expect the compilation of passing any type above Pet to fail
def upperBound[T <: Pet](t: T) = {println(t.getClass.getName)}
//I expect the compilation of passing any type below Pet to fail
def lowerBound[T >: Pet](t: T) = {println(t.getClass.getName)}
upperBound(new Dog)//Ok, As expected
upperBound(new Cat)//Ok, As expected
upperBound(new Pet)//Ok, As expected
//Won't compile (as expected) because Animal is not a sub-type of Pet
upperBound(new Animal)
lowerBound(new Pet)//Ok, As expected
lowerBound(new Animal)//Ok, As expected
//I expected this to fail because Dog is not a super type of Pet
lowerBound(new Dog)
//I expected this to fail because Lion is not a super type of Pet either
lowerBound(new Lion)
lowerBound(100)//Jesus! What's happening here?!
lowerBound(Nil)// Ok! I am out!!! :O
}

井。。。代码的最后四行对我来说没有任何意义!据我了解,下限根本不对类型参数施加任何约束。是否有隐含的绑定AnyAnyRef我错过的某个地方?

让我解释一下有界类型推断的意外行为

  1. 上限(T <:Pet):这意味着T适用于至少继承了Pet类或任何Pet子类的所有类。
  2. 下限(T>:Pet):这意味着 T 适用于继承了 Pet 类的至少一个父类的所有类

因此,正如您正确猜测的那样,AnyRef 是所有对象/引用类型的超类型。所以当我们说

lowerBound(new Dog())

狗属于类 AnyRef。因此,由于 AnyRef 是 Pet 的父级,因此编译器不会引发任何警告。

您可以看到scalaList 类的 :: 方法的类似行为。使用 List,您可以执行以下操作,而不会出现任何编译错误。

val list = List(1, 2, 3)
val newList = "Hello" :: list

如需进一步阅读,请查看这些堆栈溢出答案:

  1. https://stackoverflow.com/a/19217523/4046067
  2. https://stackoverflow.com/a/19821995/4046067

让我们看看为什么其中一些有效。您可以使用-Xprint:typer标志检查 scalac 推断的类型化。这就是我们看到的:

NewTest.this.lowerBound[tests.NewTest.Pet](new NewTest.this.Dog());
NewTest.this.lowerBound[tests.NewTest.Animal](new NewTest.this.Lion());
NewTest.this.lowerBound[Any](100)

对于Dog的情况,编译器会寻找一个符合下限要求的祖先,Pet满足自Pet >: Pet以来。对于Lion,符合要求的祖先是Animal,它从Animal >: Pet开始工作。对于最后一个,编译器推断Any,这是 Scala 类型层次结构中最高的,这也是因为Any >: Pet.

所有这些都是有效的,因为下限的定义是,任何在类型层次结构中较高的类型都可能是候选类型。这就是为什么,如果我们举一个最做作的例子,传递一个Int是有效的,因为IntPet的唯一共同祖先是Any.

您可以通过将定义更改为

def lowerBound[T](t: T)(implicit ev: Pet <:< T) = ...

这告诉编译器推断T(因此在最后三种情况下它将是LionIntNil.type),然后检查Pet是否是T的子类型,而不是推断TPet是一个子类型。

相关内容

  • 没有找到相关文章

最新更新