【问题标题】:Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?如果 A 的泛型子类型被声明为返回参数,为什么我不能返回 A 的具体子类型?
【发布时间】:2020-08-23 09:17:07
【问题描述】:
abstract class IntTree
object Empty extends IntTree
case class NonEmpty(elem: Int, left: IntTree, right: IntTree) extends IntTree

def assertNonNegative[S <: IntTree](t: S): S = {
  t match {
    case Empty => Empty  // type mismatch, required: S, found: Empty.type
    case NonEmpty(elem, left, right) =>
      if (elem < 0) throw new Exception
      else NonEmpty(elem, assertNonNegatve(left), assertNonNegative(right)) // req: S, fd: NonEmpty.type
  }
}

这是我用签名def assertNonNegative[S &lt;: IntTree](t: S): S 实现函数的失败尝试。除了将签名更改为def assertNonNegative(t: IntTree): IntTree,我找不到实现它的方法。

示例的相关性:
在“Scala 中的函数式编程原理”课程中关于子类型化和泛型(4.4)的视频中,Martin Odersky 使用了几乎相同的示例(IntSet 而不是 IntTree),并说此签名可用于表示函数采用 Empty 到Empty 和 NonEmpty 到 NonEmpty。他说其他签名在大多数情况下都很好,但如果需要,具有上限 S 的签名可能是更精确的选择。但是,他没有展示该功能的实现。

我在这里错过了什么?

【问题讨论】:

  • 编译器无法证明t的情况下是因为S =:= Empty.type。它可能是固定的,但为什么呢?你确定你甚至会有一个 NonEmpty 类型的变量吗,你可能会有一个 IntTree 类型的变量。
  • 是的,我没有充分的理由使用返回 S 的签名。当 Martin 说我可以在这种情况下使用它时,我只是想了解一下。

标签: scala generics tree pattern-matching type-bounds


【解决方案1】:

方法的右手边(模式匹配)

t match {
  case Empty => Empty 
  case NonEmpty(elem, left, right) =>
    if (elem < 0) throw new Exception
    else NonEmpty(elem, assertNonNegatve(left), assertNonNegative(right)) 
}

表示在运行时检查t是否是类Empty$(对象Empty)的实例,然后选择第一个分支,否则t是否是类NonEmpty的实例,然后选择第二个分支。

签名

def assertNonNegative[S <: IntTree](t: S): S

表示在编译时检查每个类型S,这是IntTree类型的子类型,如果该方法接受S类型的参数t,则该方法返回一个@987654335类型的值@。

代码无法编译,因为方法的定义与其签名不对应。 IntTree 的子类是 NonEmptyEmpty(对象)。如果IntTree 未密封,您可以创建不同于EmptyNonEmpty 的子类,您甚至可以在运行时动态创建它们。但我们甚至假设IntTree 是密封的,EmptyNonEmpty 是它的唯一子类。

问题是IntTree有很多子类型(类和类型是different):IntTreeEmpty.typeNonEmptyNothingNullEmpty.type with NonEmpty , NonEmpty with SomeType, Empty.type with SomeType, IntTree with SomeType, T (type T &lt;: IntTree), x.type (val x: IntTree = ???) 等等,所有这些条件都必须满足(t: S): S

显然这不是真的。例如我们可以取t = Empty.asInstanceOf[Empty.type with Serializable]。它的类型为Empty.type with Serializable。在运行时它匹配类Empty(对象),因此选择了第一个分支。但是在编译时我们还不知道这一点,如何在编译时保证返回的EmptyNonEmpty 都是Empty.type with Serializable 类型?

Type mismatch on abstract type used in pattern matching

修复assertNonNegative 的一种方法是编写诚实的单态

def assertNonNegative(t: IntTree): IntTree = {
  t match {
    case Empty => Empty
    case NonEmpty(elem, left, right) =>
      if (elem < 0) throw new Exception
      else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right))
  }
}

另一种是假装多态签名是正确的

def assertNonNegative[S <: IntTree](t: S): S = {
  (t match {
    case Empty => Empty
    case NonEmpty(elem, left, right) =>
      if (elem < 0) throw new Exception
      else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right))
  }).asInstanceOf[S]
}

三是使用类型标签

def assertNonNegative[S <: IntTree : TypeTag](t: S): S = {
  t match {
    case Empty if typeOf[S] == typeOf[Empty.type] => Empty.asInstanceOf[S]
    case NonEmpty(elem, left, right) if typeOf[S] == typeOf[NonEmpty] =>
      if (elem < 0) throw new Exception
      else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right)).asInstanceOf[S]
    case _ => ???
  }
}

第四是让ADT更加类型化

sealed trait IntTree
object Empty extends IntTree
case class NonEmpty[L <: IntTree, R <: IntTree](elem: Int, left: L, right: R) extends IntTree

并定义类型类

def assertNonNegative[S <: IntTree](t: S)(implicit ann: AssertNonNegative[S]): S = ann(t)

trait AssertNonNegative[S <: IntTree] {
  def apply(t: S): S
}
object AssertNonNegative {
  implicit val empty: AssertNonNegative[Empty.type] = { case Empty => Empty }
  implicit def nonEmpty[L <: IntTree : AssertNonNegative, 
                        R <: IntTree : AssertNonNegative]: AssertNonNegative[NonEmpty[L, R]] = {
    case NonEmpty(elem, left, right) =>
      if (elem < 0) throw new Exception
      else NonEmpty(elem, assertNonNegative(left), assertNonNegative(right))
  }
}

Soundness 的系统类型意味着有时我们在编译时拒绝某些程序,而它们在运行时不会出错。例如

val x: Int = if (true) 1 else "a"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-06
    • 2011-05-07
    • 1970-01-01
    • 1970-01-01
    • 2013-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多