【问题标题】:Scala, pattern matching on a tuple of generic trait, checking if types are equalScala,通用特征元组上的模式匹配,检查类型是否相等
【发布时间】:2015-03-22 04:32:30
【问题描述】:

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

以下代码片段被简化以展示我的案例。

所以我有一个特质

 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 的实例进行子操作,这些实例在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 方面真的很缺乏经验,所以我想获得有关这种结构的反馈。还有其他方法可以做到这一点,哪种方法最有意义?

【问题讨论】:

    标签: scala generics pattern-matching traits


    【解决方案1】:

    泛型在运行时不存在,IndexedSeq[Feature[_]] 甚至在编译时都忘记了类型参数是什么(@Jatin 的答案不允许您使用 @ 的混合类型列表构造一个 Data 987654323@)。最简单的答案可能只是捕获异常(使用来自scala.util.control.Exceptioncatchingopt)。但是,要按照书面形式回答问题:

    您可以在运行时检查类:

    case (e1: Feature[t], e2: Feature[y]) if e1.value.getClass ==
      e2.value.getClass => ...
    

    或者在Feature中包含类型信息:

    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
      }
    }
    

    更复杂的“正确”解决方案可能是使用 Shapeless 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)
    

    这样subAll 只能为所有匹配的元素的l1l2 调用,这是在编译时强制执行的。 (如果你真的想做Options,你可以在subtract中有两个ats,一个用于相同类型的Feature[T]s,一个用于不同类型的Feature[_]s,但完全排除似乎是一个更好的解决方案)

    【讨论】:

    • 比较 e1.value.getClasse2.value.getClass 在短期内解决了我的问题。 Shapeless 中的HList s 肯定很有趣,我会尝试及时升级到它,因为编译时执行对我来说听起来不错,但我需要更多地了解 Scala 的类型系统才能使用它。
    【解决方案2】:

    你可以这样做:

    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()

    【讨论】:

    • 我将@lmm 的答案标记为正确,正如他所说,这不允许我在同一个数据实例中包含不同的类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-20
    • 2018-07-18
    • 2016-10-13
    • 2019-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多