【问题标题】:Pattern Match on Case Objects with Type Members具有类型成员的案例对象的模式匹配
【发布时间】:2018-08-09 12:05:28
【问题描述】:

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 中导出给客户端的。

【问题讨论】:

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


    【解决方案1】:

    此解决方案是@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] 进行模式匹配。

    【讨论】:

    • 不错!我实际上使用标识符UniqGuarantee 作为短语“唯一ness保证”的缩写。我省略了第一个单词的结尾,因为该帖子中的所有内容都太长了。 :)
    【解决方案2】:

    所有扩展 Field 的案例都是单例对象,因此对于 Field 的每个子类型 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 的所有子类都像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)
    }
    

    此实现会进行类型检查,如果模式匹配不完整,它还会显示警告。

    诚然,该解决方案是相当重量级的,因为它要求您实现一个完整的案例类和隐式对象的单独层次结构,作为“证明”您的Fields 确实是单例。

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

    【讨论】:

      猜你喜欢
      • 2012-01-02
      • 1970-01-01
      • 2017-04-25
      • 2015-10-02
      • 2013-11-28
      • 2020-11-19
      • 2020-11-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多