如果将A的通用子类型声明为return参数,为什么不能返回A的具体子类型?

时间:2020-05-07 22:09:54

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

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 <: IntTree](t: S): S实现该功能的失败尝试。除了将签名更改为def assertNonNegative(t: IntTree): IntTree之外,我找不到实现它的方法。

示例的相关性:
在“ Scala中的函数编程原理”课程中有关子类型和泛型(4.4)的视频中,马丁·奥德斯基(Martin Odersky)实际上使用了相同的示例(IntSet代替IntTree),并说该签名可用于表示该函数将Empty用作空和不空到不空。他说,另一种签名在大多数情况下都很好,但是如果需要,具有上限S的签名可能是一个更精确的选择。但是,他没有显示该功能的实现。

我在这里想念什么?

1 个答案:

答案 0 :(得分:6)

方法的右侧(模式匹配)

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的子类型t,如果该方法接受类型S的参数S,则方法方法返回类型为IntTree的值。

由于方法的定义与其签名不符,因此无法编译代码。 NonEmpty的子类是EmptyIntTree(对象)。如果未密封Empty,则可以创建不同于NonEmptyIntTree的子类,甚至可以在运行时动态创建它们。但是,我们甚至假设Empty是密封的,而NonEmptyIntTree是其唯一的子类。

问题是IntTree有很多子类型(类和类型是different):Empty.typeNonEmptyNothing,{ {1}},NullEmpty.type with NonEmptyNonEmpty with SomeTypeEmpty.type with SomeTypeIntTree with SomeTypeTtype T <: IntTree),{{ 1}}(x.type)等条件,并且必须满足所有条件val x: IntTree = ???

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

Type mismatch on abstract type used in pattern matching

一种修复Empty.type with Serializable的方法是怀有诚实的单态性

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]
}

第四点是使ADT更具类型级别

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 _ => ???
  }
}

并定义类型类

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

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

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))
  }
}