2012-10-04 75 views
2

我有斯卡拉特徵和類型擦除的麻煩。我有這樣的特點:如何使用參數化特徵進行模式匹配?

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

現在我想使用模式匹配來檢查這種情況:

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

Tx: Meta[T]應該是yy類型應該是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 

    } 
} 

回答

0

UPDATE:在結束時加入的替代方案。

由於類型擦除,您在遇到泛型類型時遇到了模式匹配的侷限性。

然而,所有都不會丟失。我們可以依靠ClassManifests實現一個通用的方法,你的類轉換到目標類型T(以及其他類似的上轉換成元[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現在必須是一個類,因此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的實例幾乎透明地一樣有效地使用。

+0

感謝您的答案,但不幸的是,即使使用您的解決方案,我仍然得到ClassCaseException。 – schlicht

+0

上面的代碼適用於我。你能詳細說明哪些代碼完全不起作用嗎?你在做什麼比在我的代碼片段不同? –

+0

我粘貼整個代碼片段注入斯卡拉解釋,然後我跑Test.test(): Test.test() 權 java.lang.ClassCastException:富不能轉換爲 – schlicht