如果将A的通用子类型声明为return参数,为什么不能返回A的具体子类型? [英] Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?
问题描述
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空和不空到不空.他说,另一种签名在大多数情况下都可以,但是如果需要,具有上限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
表示在编译时检查类型IntTree
的子类型S
的每种类型,如果该方法接受类型S
的参数t
,则该方法返回值
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
的子类是NonEmpty
和Empty
(对象).如果未密封IntTree
,则可以创建不同于Empty
和NonEmpty
的子类,甚至可以在运行时动态创建它们.但是,我们甚至假设IntTree
是密封的,而Empty
和NonEmpty
是其唯一的子类.
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.
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
(对象)匹配,因此选择了第一个分支.但是在编译时我们还不知道这一点,如何保证在编译时返回的Empty
和NonEmpty
都具有Empty.type with Serializable
类型?
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
的方法是怀有诚实的单态性
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的通用子类型声明为return参数,为什么不能返回A的具体子类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!