【问题标题】:How do I use pattern matching with parametrized traits?如何将模式匹配与参数化特征一起使用?
【发布时间】:2012-10-08 20:25:09
【问题描述】:

我在 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")}

来自x: Meta[T]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

  }
}

【问题讨论】:

    标签: scala traits type-erasure


    【解决方案1】:

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

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

    然而,一切都没有丢失。我们可以依靠 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")
        }
      }
    }
    

    更新:如果每次混合 Meta 时都显式设置 metaManifest 不是一个选项,你可以让 scala 通过在 Metas 构造函数中隐式传递它来自动推断它。这意味着Meta 现在必须是一个类,因此AB(以及所有必须作为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,您甚至可以添加从Meta[T] 到其包装值的隐式转换,以便Meta[T] 的实例几乎可以像T 的实例一样有效地使用透明的。

    【讨论】:

    • 感谢您的回答,但不幸的是,即使使用您的解决方案,我仍然会收到 ClassCaseException。
    • 上面的代码对我有用。您能否详细说明哪些代码不起作用?你在做什么与我的代码 sn-p 不同?
    • 我将整个代码 sn-p 粘贴到 Scala 解释器中,然后运行 ​​Test.test(): Test.test() right java.lang.ClassCastException: Foo cannot be cast to A跨度>
    • 嗯,好的。试试 Test.test[A] 和 Test.test[String]。当你不提供任何类型参数时,scala 会推断出Nothing,这在这里不是很有帮助。平心而论,我的代码 sn-p 是错误的,因为它不能正常工作 T = Nothing(甚至 T = Any 和 T = AnyRef 的任何超类型)。我在这里犯了一个致命的错误,使它完全不安全。所以我更新了我的帖子以修复它。请注意,尽管我们现在必须显式地为Meta 的类型参数提供classManifest(每次我们混合它时),这非常麻烦。全面改变设计可能是个好主意。
    • 每次混入 Meta 时都提供 classManifest 是不切实际的,因为此代码将成为库和内部的一部分,因为不应公开。如何获取在运行时调用 Test.test[TYPE] 的类型?有没有通过classManifest的方法?我并没有真正理解整个 Manifest 技巧,但我会尝试理解它!谢谢!
    猜你喜欢
    • 1970-01-01
    • 2021-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-13
    • 2014-05-16
    • 2015-02-28
    相关资源
    最近更新 更多