簡體   English   中英

Scala,在通用特征元組上進行模式匹配,檢查類型是否相等

[英]Scala, pattern matching on a tuple of generic trait, checking if types are equal

我知道很多有關泛型類型的類型擦除和模式匹配的問題,但是我無法從答案中了解我該怎么辦,並且我無法在標題中更好地解釋它。

以下代碼段已簡化以展示我的情況。

所以我有個特質

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

這以某種方式起作用。 如果我嘗試對具有相同features索引中不同類型的Data實例進行子操作, ClassCastException得到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真的沒有經驗,所以我想獲得有關這種結構的反饋。 還有其他方法可以做到嗎?哪種方法最有意義?

泛型在運行時不存在, IndexedSeq[Feature[_]]甚至在編譯時就忘記了類型參數是什么(@Jatin的回答將不允許您使用Feature[_]的混合類型列表構造Data Feature[_] )。 最簡單的答案可能是剛剛捕獲異常(使用catchingoptscala.util.control.Exception )。 但是,要回答書面問題:

您可以在運行時檢查類:

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)

這樣,只能為所有元素匹配的l1l2調用subAll這是在編譯時強制執行的 (如果您確實想執行Option您可以在subtract有兩個at ,一個用於相同類型的Feature[T] ,一個用於不同類型的Feature[_] ,但是將其完全排除似乎是一個更好的選擇解)

您可以執行以下操作:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM