Scala,通用特征元组上的模式匹配,检查类型是否相等

时间:2015-01-23 07:14:20

标签: scala generics pattern-matching traits

我知道关于泛型类型的类型擦除和模式匹配存在很多问题,但是我无法理解我应该从这些问题的答案中做什么,而且我无法在标题中更好地解释它。

以下代码片段经过简化,以展示我的案例。

所以我有一个特质

 trait Feature[T] {
      value T
      def sub(other: Feature[T]): Double
 }

 // implicits for int,float,double etc to Feature with sub mapped to - function
 ...

然后我有一个班级

 class Data(val features: IndexedSeq[Feature[_]]) {
     def sub(other: Data): IndexedSeq[Double] = {
         features.zip(other.features).map {
             case(e1: Feature[t], e2: Feature[y]) => e1 sub e2.asInstanceOf[Feature[t]]
         }
     }    
 }

我有一个像这样的测试用例

case class TestFeature(val value: String) extends Feature[String] {
     def sub(other: Feature[String]): Double = value.length - other.length
}

val testData1 = new Data(IndexedSeq(8, 8.3f, 8.232d, TestFeature("abcd"))
val testData2 = new Data(IndexedSeq(10, 10.1f, 10.123d, TestFeature("efg"))

testData1.sub(testData2).zipWithIndex.foreach { 
  case (res, 0) => res should be (8 - 10)
  case (res, 1) => res should be (8.3f - 10.1f)
  case (res, 2) => res should be (8.232d - 10.123d)
  case (res, 3) => res should be (1)
}

这种方式有效。如果我尝试使用Data的{​​{1}}实例的子操作,这些实例在features的相同索引中具有不同类型,我会得到ClassCastException。这实际上满足了我的要求,但如果可能的话,我想使用Option而不是抛出异常。如何使以下代码工作?

 class Data(val features: IndexedSeq[Feature[_]]) {
     def sub(other: Data): IndexedSeq[Double] = {
         features.zip(other.features).map {
             // of course this does not work, just to give idea
             case(e1: Feature[t], e2: Feature[y]) if t == y => e1 sub e2.asInstanceOf[Feature[t]]
         }
     }    
 }

此外,我对Scala缺乏经验,所以我希望获得有关此类结构的反馈。还有其他方法可以做到这一点,哪种方式最有意义?

2 个答案:

答案 0 :(得分:2)

泛型不存在于运行时,IndexedSeq[Feature[_]]已经忘记了即使在编译时类型参数是什么(@ Jatin的答案不会让你构建一个{ {1}}包含Data的混合类型列表。最简单的答案可能就是捕获异常(使用Feature[_]中的catchingopt)。但是,要回答所写的问题:

您可以在运行时检查类:

scala.util.control.Exception

或在case (e1: Feature[t], e2: Feature[y]) if e1.value.getClass == e2.value.getClass => ... 中包含类型信息:

Feature

更复杂"正确"解决方案可能是使用Shapeless trait Feature[T] { val value: T val valueType: ClassTag[T] // write classOf[T] in subclasses def maybeSub(other: Feature[_]) = other.value match { case valueType(v) => Some(actual subtraction) case _ => None } } 来保留列表中的类型信息:

HList

这样// note the type includes the type of all the elements val l1: Feature[Int] :: Feature[String] :: HNil = f1 :: f2 :: HNil val l2 = ... // a 2-argument function that's defined for particular types // this can be applied to `Feature[T], Feature[T]` for any `T` object subtract extends Poly2 { implicit def caseFeatureT[T] = at[Feature[T], Feature[T]]{_ sub _} } // apply our function to the given HLists, getting a HList // you would probably inline this // could follow up with .toList[Double] // since the resulting HList is going to be only Doubles def subAll[L1 <: HList, L2 <: HList](l1: L1, l2: L2)( implicit zw: ZipWith[L1, L2, subtract.type]) = l1.zipWith(l2)(subtract) 只能调用subAlll1所有元素匹配的,这在编译时强制执行。 (如果你真的想要l2Option可以有两个at,一个用于同类型subtract,另一个用于不同类型Feature[T] 1}} s,但完全排除它似乎是一个更好的解决方案)

答案 1 :(得分:1)

你可以这样做:

class Data[T: TypeTag](val features: IndexedSeq[Feature[T]]) {

    val t = implicitly[TypeTag[T]]

    def sub[E: TypeTag](other: Data[E]): IndexedSeq[Double] = {
        val e = implicitly[TypeTag[E]]
        features.zip(other.features).flatMap{
            case(e1, e2: Feature[y]) if e.tpe == t.tpe  => Some(e1 sub e2.asInstanceOf[Feature[T]])
            case _ => None
        }
    }
}

然后:

case class IntFeature(val value: Int) extends Feature[Int] {
    def sub(other: Feature[Int]): Double = value - other.value
}
 val testData3 = new Data(IndexedSeq(TestFeature("abcd")))
 val testData4 = new Data(IndexedSeq(IntFeature(1)))
 println(testData3.sub(testData4).zipWithIndex)

给出Vector()