具有类型成员的案例对象的模式匹配

时间:2018-08-09 12:05:29

标签: scala generics types pattern-matching type-members

Scala具有一个很好的功能,可以在模式匹配中推断类型参数。它还检查模式匹配的详尽性。例如:

sealed trait PField[T]

case object PField1 extends PField[String]

case object PField2 extends PField[Int]

def getValue[X](f: PField[X]): X = f match {
  case PField1 => "aaa"
  case PField2 => 123
}

是否可以实现相同的功能,但使用类型成员而不是类型参数?

sealed trait Field {
  type T
}

case object Field1 extends Field {
  type T = String
}

case object Field2 extends Field {
  type T = Int
}

以下解决方案不起作用(在Scala 2.12.6中测试):

//No exhaustiveness check
def getValue[X](f: Field {type T = X}): X = f match {
  case Field1 => "aaa"
  //    case Field2 => 123
}

//casting needed
def getValue2[X](f: Field {type T = X}): X = (f: Field) match {
  case Field1 => "aaa".asInstanceOf[X]
  case Field2 => 123.asInstanceOf[X]
}

type Generified[X] = Field {type T = X}

//No exhaustiveness check
def getValue3[X](f: Generified[X]): X = f match {
  case Field1 => "aaa"
  //    case Field2 => 123
}

在我的情况下,类型参数确实存在问题,因为我有许多字段子层次结构,并且每个层次结构都有一些类型类。我不能将所有需要的依赖项放在case对象的内部,因为它们以瘦JAR格式导出到客户端。

2 个答案:

答案 0 :(得分:3)

此解决方案是@Andrey Tyukin发布的解决方案的简化版本。 他说

  

对于Field3类型的任何两个值a,b,不认为a.T = b.T。

这意味着要进行详尽的模式匹配,必须忽略类型成员。因此,为了同时具有穷举性和类型推断,我们需要具有类型参数的密封层次结构。

他建议为案例类和模式匹配创建单独的层次结构,而不是在主层次结构上进行模式匹配。但是,在我的情况下,它可以简化:我使用类型参数创建了新的密封特征,但是相同的案例对象用于模式匹配(“唯一保证”保留在对象本身中)。这是最终解决方案:

sealed trait Field {
  type T
  def ug: TField[T]
}

sealed trait TField[G] extends Field {
  type T = G
  def ug: TField[T] = this
}

case object Field1 extends TField[String]
case object Field2 extends TField[Int]

def getValue[X](f: Field {type T = X}): X = (f.ug: TField[X]) match {
  case Field1 => "abc"
  case Field2 => 123
}

由于我可以使用Field特征来定义类型类,而无需进入更高种类的类型,而可以切换到TField[G]进行模式匹配。

答案 1 :(得分:2)

所有扩展Field的案例都是单例对象,因此对于F的每个子类型Field都成立:

 if
    a: F, b: F, b.T = X
 then
    a.T = X

这通常不适用,例如

class Bar { type Q }
case class Field3(b: Bar) extends Field { type T = b.Q }

对于类型为a.T = b.T的任意两个值a, b,不会保留Field3

因此,您必须以某种方式保证Field的所有子类的行为都像Field1Field2一样,并且不像假设的Field3。您可以通过在getValue上添加一个隐式参数来执行此操作,以证明该字段的行为良好。例如,证明该字段确实是一个单例对象就足够了。

这是一个粗略的草图:

sealed trait Field { type T }
case object Field1 extends Field { type T = String }
case object Field2 extends Field { type T = Int }

sealed trait UniqGuarantee[UniqueTypeAsPathDependent]
case class S1UG[X](f: String =:= X) extends UniqGuarantee[X]
case class S2UG[X](f: Int =:= X) extends UniqGuarantee[X]

sealed trait IsSingleton[F <: Field] {
  def apply(singleton: F): UniqGuarantee[singleton.T]
}

implicit object F1_is_Singleton extends IsSingleton[Field1.type] {
  def apply(singleton: Field1.type): UniqGuarantee[singleton.T] = 
    S1UG(implicitly)
}

implicit object F2_is_Singleton extends IsSingleton[Field2.type] {
  def apply(singleton: Field2.type): UniqGuarantee[singleton.T] =
    S2UG(implicitly)
}

def getValue[F <: Field]
  (f: F)
  (implicit sing: IsSingleton[F])
: f.T = sing(f) match {
  case S1UG(uniqGuarantee) => uniqGuarantee("abc")
  case S2UG(uniqGuarantee) => uniqGuarantee(123)
}

此实现会进行类型检查,并且如果模式匹配不够详尽,还会显示警告。

诚然,该解决方案非常笨重,因为它要求您实现案例类和隐式对象的整个独立层次结构,这些对象充当“证明”,证明Field确实是单例。

我认为解决方案可以缩短很多,我只是现在不知道。