如果 A 的泛型子类型声明为返回参数,为什么我不能返回 A 的具体子类型? [英] Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?

查看:25
本文介绍了如果 A 的泛型子类型声明为返回参数,为什么我不能返回 A 的具体子类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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 之外,我找不到实现它的方法.

This is my failed attempt of implementing the function with signature def assertNonNegative[S <: IntTree](t: S): S. Other than changing the signature to def assertNonNegative(t: IntTree): IntTree, I couldn't find a way to implement it.

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

Relevance of example:
In a video about subtyping and generics(4.4) in the course "Functional Programming Principles in Scala", Martin Odersky uses practically the same example (IntSet instead of IntTree) and says that this signature can be used to express that the function takes Empty to Empty and NonEmpty to NonEmpty. He says that the other signature is fine in most situations but if needed, the one with upper bounded S can be a more precise option. However, he does not show an implementation of the function.

我在这里遗漏了什么?

推荐答案

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

Method's right hand side (pattern matching)

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 类的实例,然后选择第二个分支.

means to check at runtime whether t is an instance of class Empty$ (object Empty) and then choose the first branch or otherwise whether t is an instance of class NonEmpty and then choose the second branch.

签名

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

表示在编译时检查每个类型S,它是IntTree类型的子类型,如果方法接受参数tS 类型的方法返回一个 S 类型的值.

means to check at compile time that for every type S, which is a subtype of type IntTree, if the method accepts parameter t of type S then the method returns a value of type S.

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

The code doesn't compile because definition of the method doesn't correspond to its signature. Subclasses of IntTree are NonEmpty and Empty (object). If IntTree is not sealed you can create its subclasses different from Empty and NonEmpty, you can even create them dynamically at runtime. But let's even suppose that IntTree is sealed and Empty and NonEmpty are its only subclasses.

问题是IntTree有很多子类型(类和类型是不同): IntTree, Empty.type, NonEmpty,NothingNullEmpty.type with NonEmptyNonEmpty with SomeTypeEmpty.type with SomeType, IntTree with SomeType, T (type T <: IntTree), x.type(val x: IntTree = ???) 等,并且对于所有这些条件 (t: S): S 必须满足.

The thing is that there are a lot of subtypes of IntTree (classes and types are different): IntTree, Empty.type, NonEmpty, Nothing, Null, Empty.type with NonEmpty, NonEmpty with SomeType, Empty.type with SomeType, IntTree with SomeType, T (type T <: IntTree), x.type (val x: IntTree = ???) etc. and for all of them condition (t: S): S must be fulfilled.

显然这不是真的.例如,我们可以采用 t = Empty.asInstanceOf[Empty.type with Serializable].它的类型为 Empty.type with Serializable.在运行时它匹配类 Empty (object) 所以第一个分支被选中.但是在编译时我们还不知道这一点,你怎么能在编译时保证返回的 EmptyNonEmpty 都是 Empty.type 类型可序列化?

Obviously it's not true. For example we can take t = Empty.asInstanceOf[Empty.type with Serializable]. It has type Empty.type with Serializable. At runtime it matches class Empty (object) so the first branch is selected. But at compile time we don't know this yet, how can you guarantee at compile time that both Empty and NonEmpty that are returned have type Empty.type with Serializable?

模式匹配中使用的抽象类型的类型不匹配

修复assertNonNegative 的一种方法是使用诚实的monomorphic

One way to fix assertNonNegative is to wright honest monomorphic

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

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

another is to pretend that polymorphic signature is correct

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更加类型化

the fourth is to make ADT more type-level

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 of type system means that sometimes we reject some programs at compile time, while they can't go wrong at runtime. For example

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

这篇关于如果 A 的泛型子类型声明为返回参数,为什么我不能返回 A 的具体子类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆