【问题标题】:Scala lower bound does not behave as I expectScala 下限的行为不像我预期的那样
【发布时间】:2018-08-21 20:26:26
【问题描述】:

今天我花了几个小时来了解 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
}

嗯...最后四行代码对我来说没有任何意义!据我了解,Lower Bound 根本不对类型参数施加任何限制。是否有隐含的绑定到AnyAnyRef 我错过的某处?

【问题讨论】:

    标签: scala lower-bound upperbound


    【解决方案1】:

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

    1. Upper Bound(T <: pet t>
    2. Lo​​wer Bound(T >: Pet):这意味着 T 适用于所有继承了至少一个 Pet 类的父类的类。

    所以你猜对了,AnyRef 是所有对象/引用类型的超类型。所以当我们说

    lowerBound(new Dog())
    

    Dog 属于 AnyRef 类。所以通过下限,由于 AnyRef 是 Pet 的父级,编译器不会抛出任何警告。

    您可以看到 scala List 类的 :: 方法的类似行为。使用 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

    【讨论】:

    • 非常感谢 tawkir 的详细回答。这根本不是我所期望的。在那种情况下,说[T &gt;: 'Any Arbitrary Type'] 总是在逻辑上与[T &lt;: Any] 相同是正确的说法吗?如果这是真的,那么下限似乎根本不是一个约束,它可以写成[Any][ _ ](理论上)!
    • @AlexGolshani 不,它们根本不等价:编译器将为这些约束推断出不同的Ts。例如。在lowerBound(new Dog)T &gt;: Pet 中推断Animal,在T &lt;: Any 中推断Dog。当T 是返回类型的一部分时,这很重要。
    【解决方案2】:

    让我们看看为什么其中一些有效。您可以使用 -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 的情况,编译器会寻找与下限要求相匹配的祖先,PetPet &gt;: Pet 起就满足该下限要求。对于Lion,匹配要求的祖先是Animal,它从Animal &gt;: Pet 开始工作。对于最后一个,编译器推断出Any,它是Scala 类型层次结构中最高的,这也是因为Any &gt;: Pet

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

    【讨论】:

    • 感谢@Yuval 的回答。谢了哥们。参考您的评论,“这就是为什么,如果我们采取最人为的例子,传递一个 Int 有效,因为 Int 和 Pet 的唯一共同祖先是 Any”,因为一切都继承自 Any 这意味着只要你放那里有一个下限约束(任何较低的类型),任何 Scala 类型都将满足条件,我做对了吗?我只是将[T &gt;: Any][T &gt;: Int][T &gt;: Null] 放在lowerBound 函数上,每次都得到相同的行为!
    • @AlexGolshani 当你设置一个下限时,你会说“我想要一个不低于Pet 的类型,没有更具体的意思。你没有说任何关于这个T 的上限,所以是的,Any 将在这里工作。
    【解决方案3】:

    您可以通过将定义更改为来获得预期的结果

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

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

    【讨论】:

      猜你喜欢
      • 2021-12-10
      • 1970-01-01
      • 1970-01-01
      • 2020-03-11
      • 2016-01-28
      • 2018-12-15
      • 1970-01-01
      • 2017-04-11
      • 1970-01-01
      相关资源
      最近更新 更多