2

我在 Scala 特征和类型擦除方面遇到了麻烦。我有这个特点:

trait Meta[T] {
  def ~=(e: T): Boolean
}

现在我想使用模式匹配来检查这种情况:

(m,i) match {
case (x:Meta[T], y: T) if x ~= y => println ("right")
case _ => println ("wrong")}

Tfromx: Meta[T]应该是 的类型或者y应该y是 的子类型T。如果类型不匹配,我会得到一个ClassCastException. 但x ~= y如果类型不正确,则不应执行。有没有解决这个问题,还是我必须捕获异常并以这种方式处理它?

我做了一个尽可能短的运行示例:

trait Meta[T] {
  type t = T
  def ~=(e: T): Boolean
}

sealed abstract class A 
case class Ide(s: String) extends A
case class MIde(s: String) extends A with Meta[A] {
  def ~=(e: A) = e match {
    case e: Ide => true
    case e: MIde => false
  }
}
sealed abstract class B 
case class Foo(s: String) extends B

object Test {

  def m = MIde("x")
  def i = Ide("i")
  def f = Foo("f")

  def main[T](args: Array[String]) {
    (m, i) match {
      case (x: Meta[T], y: T) if x ~= y => println("right")
      case _ => println("wrong")
    }
    // -> right
    (m, f) match {
      case (x: Meta[T], y: T) if x ~= y => println("right")
      case _ => println("wrong")
    }
    // -> Exception in thread "main" java.lang.ClassCastException: 
    // Examples.Foo cannot be cast to Examples.A

  }
}
4

1 回答 1

0

更新:最后添加了替代方案。

由于类型擦除,您在泛型类型方面遇到了模式匹配的限制。

然而,一切都没有丢失。我们可以依靠 ClassManifests 实现一个通用方法来将你的类转换为目标类型 T(以及另一个类似的转换为 Meta[T]):

trait Meta[T] { this: T =>
  type t = T
  def metaManifest: ClassManifest[T]
  def ~=(e: T): Boolean
}

abstract sealed class Base {
  def as[T:ClassManifest]: Option[T] = {
    if ( classManifest[T].erasure.isAssignableFrom( this.getClass ) ) Some( this.asInstanceOf[T] )
    else None
  }
  def asMeta[T:ClassManifest]: Option[T with Meta[T]] = {
    this match {
      case meta: Meta[_] if classManifest[T] <:< meta.metaManifest => as[T].asInstanceOf[Option[T with Meta[T]]]
      case _ => None
    }
  }
}
abstract sealed class A extends Base
case class Ide(s: String) extends A
case class MIde(s: String) extends A with Meta[A] {
  val metaManifest = classManifest[A]
  def ~=(e: A) = e match {
    case e: Ide => true
    case e: MIde => false
  }
}
sealed abstract class B extends Base
case class Foo(s: String) extends B

让我们在 REPL 中测试一下:

scala> m.as[A]
res17: Option[A] = Some(MIde(x))
scala> m.asMeta[A]
res18: Option[A with Meta[A]] = Some(MIde(x))
scala> i.as[A]
res19: Option[A] = Some(Ide(i))
scala> i.asMeta[A]
res20: Option[A with Meta[A]] = None
scala> f.as[A]
res21: Option[A] = None
scala> f.asMeta[A]
res22: Option[A with Meta[A]] = None

听起来不错。现在我们可以重写我们的模式匹配:

(m, i) match {
  case (x: Meta[T], y: T) if x ~= y => println("right")
  case _ => println("wrong")
}

对此:

(m.asMeta[T], i.as[T]) match {
  case (Some(x), Some(y)) if x ~= y => println("right")
  case _ => println("wrong")
}

所以你的例子现在看起来像这样:

object Test {

  val m = MIde("x")
  val i = Ide("i")
  val f = Foo("f")

  def test[T:ClassManifest]() {
    (m.asMeta[T], i.as[T]) match {
      case (Some(x), Some(y)) if x ~= y => println("right")
      case _ => println("wrong")
    }
    // -> right
    (m.asMeta[T], f.as[T]) match {
      case (Some(x), Some(y)) if x ~= y => println("right")
      case _ => println("wrong")
    }
  }
}

更新:如果metaManifest每次混合时都显式设置Meta不是一个选项,您可以让 scala 通过在Meta构造函数中隐式传递它来自动推断它。这意味着Meta现在必须是一个类,因此Aand B(以及必须作为 的类型参数出现的所有类似类型Meta)现在必须是特征,因为您不能混合 2 个类。因此,您基本上是在将一个限制换成另一个限制。选择你最喜欢的。开始了:

abstract sealed class Meta[T]( implicit val metaManifest: ClassManifest[T] ) { this: T =>
  type t = T
  def ~=(e: T): Boolean
}

trait Base {
  def as[T:ClassManifest]: Option[T] = {
    if ( classManifest[T].erasure.isAssignableFrom( this.getClass ) ) Some( this.asInstanceOf[T] )
    else None
  }
  def asMeta[T:ClassManifest]: Option[T with Meta[T]] = {
    this match {
      case meta: Meta[_] if classManifest[T] != ClassManifest.Nothing && classManifest[T] <:< meta.metaManifest => as[T].asInstanceOf[Option[T with Meta[T]]]
      case _ => None
    }
  }
}

trait A extends Base
case class Ide(s: String) extends A
case class MIde(s: String) extends Meta[A] with A {
  def ~=(e: A) = e match {
    case e: Ide => true
    case e: MIde => false
  }
}
trait B extends Base
case class Foo(s: String) extends B

object Test {
  val m = MIde("x")
  val i = Ide("i")
  val f = Foo("f")

  def test[T:ClassManifest]() {
    (m.asMeta[T], i.as[T]) match {
      case (Some(x), Some(y)) if x ~= y => println("right")
      case _ => println("wrong")
    }
    (m.asMeta[T], f.as[T]) match {
      case (Some(x), Some(y)) if x ~= y => println("right")
      case _ => println("wrong")
    }
  }
}

最后,如果两种解决方案都不适合您,您可以尝试另一种解决方案:不要与 混合Meta[T]T只需将其包装即可。Meta[T]然后将只是包装到T,您甚至可以添加一个隐式转换 fromMeta[T]到它的包装值,以便可以像几乎透明Meta[T]的实例一样有效地使用 的实例。T

于 2012-10-04T22:49:17.133 回答