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
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 确实缺乏经验,所以我想获得有关此类结构的反馈。还有其他方法可以做到这一点吗?哪种方法最有意义?
你可以这样做:
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()
泛型在运行时不存在,IndexedSeq[Feature[_]]
甚至在编译时就忘记了类型参数是什么(@Jatin 的回答不允许你用Feature[_]
的混合类型列表)。最简单的答案可能只是捕获异常(使用 catching
和 scala.util.control.Exception
中的 opt
)。但是,要回答所写的问题:
您可以在运行时检查 类:
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
}
}
更复杂的 "proper" 解决方案可能是使用 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
只能在 l1
和 l2
所有元素匹配时调用,这是在编译时强制执行的 . (如果你真的想做 Option
s,你可以在 subtract
中有两个 at
s,一个用于相同类型的 Feature[T]
s,一个用于不同类型的 Feature[_]
s,但完全排除它似乎是更好的解决方案)
我知道关于泛型类型的类型擦除和模式匹配存在很多问题,但我无法从这些答案中理解在我的情况下我应该做什么,而且我无法在标题中更好地解释它。
以下代码片段经过简化以展示我的案例。
所以我有一个特点
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
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 确实缺乏经验,所以我想获得有关此类结构的反馈。还有其他方法可以做到这一点吗?哪种方法最有意义?
你可以这样做:
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()
泛型在运行时不存在,IndexedSeq[Feature[_]]
甚至在编译时就忘记了类型参数是什么(@Jatin 的回答不允许你用Feature[_]
的混合类型列表)。最简单的答案可能只是捕获异常(使用 catching
和 scala.util.control.Exception
中的 opt
)。但是,要回答所写的问题:
您可以在运行时检查 类:
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
}
}
更复杂的 "proper" 解决方案可能是使用 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
只能在 l1
和 l2
所有元素匹配时调用,这是在编译时强制执行的 . (如果你真的想做 Option
s,你可以在 subtract
中有两个 at
s,一个用于相同类型的 Feature[T]
s,一个用于不同类型的 Feature[_]
s,但完全排除它似乎是更好的解决方案)