在类型层次结构中,从参数到返回类型的 Scala 类型多态性

Posted

技术标签:

【中文标题】在类型层次结构中,从参数到返回类型的 Scala 类型多态性【英文标题】:Scala type polymorphism from parameter to return type, within type hierarchy 【发布时间】:2015-07-10 01:03:30 【问题描述】:

我有一些Collection[SuperType] 类型的集合。这个集合中存储了几个值,它们是SuperType 的子类型,我希望集合只允许自己包含每个子类型的一个实例(有点像集合,但不是)。

我正在尝试编写一个函数,当给定上述子类型之一的伴生对象时,它可以返回伴生对象所属类的第一个实例。

最初我尝试使用如下所示的 Set,但 T 会遭受类型擦除,因此模式匹配将失败。然后我也意识到 Set 不适合这个任务,因为我只希望集合中的每个子类型出现一次。

def get[T <: SuperType](target: T): Option[SuperType] =
  collection.collectFirst(
    case target: T => target
  )

我的下一个也是当前的方法是使用映射,其中键是伴随对象,值是伴随对象类的实例。类型层次如下所示。

trait SuperType
trait SuperTypeValue

// Pretend this has some parameters
case class ExampleSubType extends SuperTypeValue

case object ExampleSubType extends SuperType 
  // value for use in later example
  val uniqueToObjectField: String = "hello"


val collection: Map[SuperType, SuperTypeValue] = // Some new map

def get(target: SuperType): Option[SuperTypeValue] =
  collection.get(target)

上面的效果很好。但是,我想保留用作参数的子类型的类型,并将其用作返回类型。我相信函数的签名看起来像这样:

get[T <: SuperType](target: T): Option[T]

// So I could then do something like this
get(ExampleSubType) match 
  case Some(exampleSubType) => exampleSubType.uniqueToObjectField
  case _ => "nope"

这在scala中可能吗?如果是这样,怎么做?如果没有,这在其他语言中是否存在?它叫什么?

希望这个问题没有明显的问题,但现在是凌晨 2 点,所以我会在早上再检查一遍。

【问题讨论】:

难道不能简单地重新定义equals方法,如果两个实例相同则返回true,然后直接使用Set吗? 好主意,应该可以。不知道我对劫持 equals 方法的感觉如何。 【参考方案1】:

您可以使用ClassTags 绕过类型擦除。与其使用伴生对象,不如直接提供泛型参数更容易:

import scala.reflect._

trait SuperType  val x: Int 

case class Foo(x: Int) extends SuperType
case class Bar(x: Int) extends SuperType

val collection = Set(Foo(1), Foo(2), Bar(3), Foo(4), Bar(5))
def get[T <: SuperType : ClassTag]: Option[T] = 
    collection.collectFirst 
        case target: T => target
    

然后你可以调用:

get[Foo] //Foo(1)
get[Bar] //Bar(3)

【讨论】:

谢谢,很高兴我可以使用类型参数!我已将 def get[T &lt;: SuperType : ClassTag]: Option[SuperType] 更改为 def get[T &lt;: SuperType : ClassTag]: Option[T],因此我可以访问可能未由 SuperType 定义的成员,并且对于子类型是唯一的。 哦,那当然好多了!我已经更新了答案。【参考方案2】:

当您需要锯子时,您会尝试使用锤子。您应该为此创建一个新类,并为每种类型创建一个字段。

class SomeClass
 a:TypeA
 b:TypeB
 c:TypeC

// if c extends b extends a maybe you want
// to prevent a TypeC being assigned to A I have no idea
// you can change these implementations to get the behavior you want
 addA(a:TypeA) this.a = a
 addB(b:TypeB) this.b = b
 addC(c:TypeC) this.c = c


新手经常会出于疯狂的目的尝试使用集合。仅仅因为一个集合包含数据,并不意味着您想在任何时候都需要一个数据。在决定要使用什么之前,您需要首先考虑您的需求是什么,而不是反过来,如果您采用这种方法,您将在余下的编程生命中使用 SO。

【讨论】:

我应该在我的问题中明确说明这一点,但我无法明确知道所有子类型。如果某个库的用户想要为自己的自定义子类型扩展超类型怎么办?所以我想用你的锯子,但锤子更适合我的用例。

以上是关于在类型层次结构中,从参数到返回类型的 Scala 类型多态性的主要内容,如果未能解决你的问题,请参考以下文章

如何创建类型化工厂方法构造函数的类层次结构并使用抽象类型从 Scala 访问它们?

Scala 类型层次结构

在 Scala 中使用案例类创建层次结构

将 ADT / 密封特征层次结构编码到 Spark DataSet 列中

scala-从入门到精通

scala的基础概念