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

Scala 具有类型成员的案例对象上的模式匹配,scala,generics,types,pattern-matching,type-members,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 => 12

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中测试):


在我的例子中,类型参数确实有问题,因为我有许多字段的子层次结构,每个层次结构都有一些类型类。我不能将所有需要的依赖项都放在case对象中,因为它们是在瘦JAR中导出到客户端的

扩展
字段
的所有案例都是单例对象,因此对于
字段
的每个子类型
F
,它都包含:

 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 }
对于类型为
Field3
的任何两个值
a,b
,它不认为
a.T=b.T

因此,您必须以某种方式保证
Field
的所有子类都像
Field1
Field2
一样表现良好,并且它们不像假设的
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)
}
密封特征字段{type T}
案例对象字段1扩展字段{type T=String}
案例对象字段2扩展字段{type T=Int}
密封特征UniqGuarrance[UniqueTypeAsPathDependent]
案例类S1UG[X](f:String=:=X)扩展了UniqGuarrance[X]
案例类S2UG[X](f:Int=:=X)扩展了UniqGuarrance[X]
密封单体[F统一担保(123)
}
这个实现进行类型检查,如果模式匹配不是详尽的,它还会显示警告

诚然,这个解决方案非常重要,因为它需要实现一个完整的独立的case类和隐式对象的层次结构,作为
字段
确实是单例的“证明”


我认为这个解决方案可以缩短很多,我只是不知道现在该怎么做。

这个解决方案是@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
trait来定义类型类,而无需进入更高级的类型,并切换到
TField[G]
进行模式匹配。

Nice!我实际上使用了标识符
uniqguarange
作为短语“唯一性保证”的缩写.我省略了第一个单词的结尾,因为那篇帖子的内容都有点太长了。:)
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
}