2013-04-17 31 views
25

讓我們假設我們有一個泛型類Container如何在Scala中對泛型進行模式匹配?

case class Container[+A](value: A) 

然後我們要匹配模式與Double一個ContainerAny一個Container

val double = Container(3.3) 
var container: Container[Any] = double 

要做到這一點,我們通常會寫:

container match { 
    case c: Container[String] => println(c.value.toUpperCase) 
    case c: Container[Double] => println(math.sqrt(c.value)) 
    case _ => println("_") 
} 

但是,編譯器給出了兩個警告,前兩種情況各有一種。例如,第一個警告說:「類型模式Container [String]中的非變量類型參數String未被選中,因爲它被擦除消除」。由於擦除,在運行時不可能區分不同類型的容器,並且第一次捕獲將匹配。因此,Container[Double]類型的容器將與第一種情況相匹配,這會捕獲Container[String]對象,因此toUpperCase方法將在Double上調用,並且將拋出java.lang.ClassCastException

如何匹配由特定類型參數化的Container

+0

我已經在那裏添加了對同一個問題的答案:[link]( http://stackoverflow.com/questions/35181533/can-we-elegantly-match-an-erased-type-in​​-scala/35181934#35181934) – dth

回答

11

對此可能的解決方法可能是使用isInstanceOfasInstanceOf

container match { 
    case Container(x) if x.isInstanceOf[String] => 
    println(x.asInstanceOf[String].toUpperCase) 
    case Container(x) if x.isInstanceOf[Double] => 
    println(math.sqrt(x.asInstanceOf[Double])) 
    case _ => println("_") 
} 

這樣的工作,但它看起來並不高雅。 Scala的創建者Martin Odersky教授說應該避免使用isInstanceOfasInstanceOf

正如羅布·諾里斯指出我出去,在球場上從Coursera「斯卡拉函數編程」的論壇,通過類型的匹配是一個不好的做法:case foo: Bar => ...。 Scala鼓勵利用靜態類型並避免在運行時檢查類型。這與Haskell/ML世界的哲學一致。而不是匹配類型,case子句應該匹配構造函數

爲了解決Container匹配問題,對於每種類型的特殊容器可以被定義:

class Container[+A](val value: A) 

case class StringContainer(override val value: String) 
    extends Container(value) 

case class DoubleContainer(override val value: Double) 
    extends Container(value) 

現在構造將被匹配,而不是類型

container match { 
    case StringContainer(x) => println(x.toUpperCase) 
    case DoubleContainer(x) => println(math.sqrt(x)) 
    case _ => println("_") 
} 

顯然,我們可以在兩個對象中定義unapply方法,StringContainerDoubleContainer並使用與上面相同的匹配,inst擴展Container類的EAD:

case class Container[+A](val value: A) 

object StringContainer { 
    def unapply(c: Container[String]): Option[String] = Some(c.value) 
} 


object DoubleContainer { 
    def unapply(c: Container[Double]): Option[Double] = Some(c.value) 
} 

但是,這並不工作,再次,因爲JVM類型擦除。

羅布諾里斯的職位,引導我回答這個問題可以在這裏找到:https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567。不幸的是,除非您參加Coursera課程,否則您無法訪問它。

24

也許這將幫助

def matchContainer[A: Manifest](c: Container[A]) = c match { 
     case c: Container[String] if manifest <:< manifest[String] => println(c.value.toUpperCase) 
     case c: Container[Double] if manifest <:< manifest[Double] => println(math.sqrt(c.value)) 
     case c: Container[_] => println("other") 
    } 

編輯:

由於Impredicative指出,清單已被棄用。相反,你可以做到以下幾點:

import reflect.runtime.universe._ 
def matchContainer[A: TypeTag](c: Container[A]) = c match { 
     case c: Container[String] if typeOf[A] <:< typeOf[String] => println("string: " + c.value.toUpperCase) 
     case c: Container[Double] if typeOf[A] <:< typeOf[Double] => println("double" + math.sqrt(c.value)) 
     case c: Container[_] => println("other") 
    } 
+3

值得關注的是'Manifest'是有利於TypeTag'的'棄用最新版本的Scala。 – Impredicative

+2

應該是線程安全的現在: https://github.com/scala/scala/pull/3386 – Cal

29

一般rarry的答案是正確的,對於您的情況但它可以簡化,因爲你的容器只包含一個通用型的單個值,所以你可以匹配該值的直接鍵入:

container match { 
    case Container(x: String) => println("string") 
    case Container(x: Double) => println("double") 
    case _ => println("w00t") 
} 
3

注意:您還可以用Miles SabinShapeless libraryalready mentioned by Miles in 2012 here)的替代品。

你可以看到從Jaakko Pallari

Typeable在「Ways to pattern match generic types in Scala」的一個例子是一種類,它提供了從Any類型轉換值到特定類型的的能力。
鑄造操作的結果是一個Option其中Some值將包含成功鑄造值,並且None值表示的鑄造故障。

TypeCaseTypeable和模式匹配。它本質上是爲Typeable實例的提取

import shapeless._ 

def extractCollection[T: Typeable](a: Any): Option[Iterable[T]] = { 
    val list = TypeCase[List[T]] 
    val set = TypeCase[Set[T]] 
    a match { 
    case list(l) => Some(l) 
    case set(s) => Some(s) 
    case _  => None 
    } 
} 

val l1: Any = List(1, 2, 3) 
val l2: Any = List[Int]() 
val s: Any = Set(1, 2, 3) 

extractCollection[Int](l1) // Some(List(1, 2, 3)) 
extractCollection[Int](s)  // Some(Set(1, 2, 3)) 
extractCollection[String](l1) // None 
extractCollection[String](s) // None 
extractCollection[String](l2) // Some(List()) // Shouldn't this be None? We'll get back to this. 

雖然Typeable可能看起來像它有什麼需要解決類型擦除,它仍然受到同樣的行爲,任何其他運行時代碼。
這可以在那裏空名單被公認爲即使他們被指定爲整數列表字符串列表前面的代碼示例中的最後一行可以看出。這是因爲Typeable鑄件是基於列表的值。如果列表爲空,那麼當然這是一個有效的字符串列表和一個有效的整數列表(或任何其他列表)

+0

鏈接的文章確實給這個主題的一個很好的治療,同時也涵蓋了其他替代品(運行時反射等) – Luciano