Scala下限不符合我的预期

时间:2017-03-19 10:38:38

标签: scala lower-bound upperbound

今天我花了几个小时来理解Scala中Lower Bounds背后的逻辑,但是我读的越多,它就会越混乱。请你谈谈这个问题吗?

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

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的隐含绑定吗?

3 个答案:

答案 0 :(得分:2)

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

  1. 上限(T&lt;:Pet):这意味着T适用于至少继承Pet类或任何Pet子类的所有类。
  2. 下界(T&gt;:Pet):这意味着T适用于所有继承了至少一个Pet类的父类的类。
  3. 正如您所猜测的那样, AnyRef是所有对象/引用类型的超级类型。所以当我们说

    lowerBound(new Dog())
    

    Dog属于AnyRef类。因此,通过下限,由于AnyRef是Pet的父级,因此编译器不会发出警告。

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

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

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

    1. SQL injection
    2. https://stackoverflow.com/a/19217523/4046067

答案 1 :(得分:1)

让我们看看为什么其中一些有用。您可以使用-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

答案 2 :(得分:0)

您可以通过将定义更改为

来获得预期结果
def lowerBound[T](t: T)(implicit ev: Pet <:< T) = ...

这告诉编译器推断T(在最后三种情况下它将是LionIntNil.type)并且然后检查PetT的子类型,而不是推断T ,使 Pet成为子类型。