2013-05-29 39 views
1

我寫了這樣的方法:Scala:可選的類型參數?

def typeOnly[T, S](seq: Seq[S]): Seq[T] = { 
    seq.flatMap{ 
    case t: T => Some(t) 
    case _ => None 
    } 
} 

,我希望通過這種方式來調用它:

typeOnly[String](List(1, "2", 3, "4")) 

它不工作。我似乎已經指定第2類參數S

typeOnly[String, Any](List(1, "2", 3, "4")) 

但是,爲什麼?編譯器是否應該知道List(1, "2", 3, "4")Seq[Any]的事實?

+3

編譯** **可以推斷出一個事實,即'列表(1,「2」,3「 4「)'是'Seq [Any]',但它不能忍受你忽略忘記指定第二種類型的想法。請注意,您的匹配是無用的,因爲Seq類型在運行時被刪除。 –

+0

嗯,你說得對。是否有可能用類類型重寫此方法? –

+0

如果你願意成爲類型安全的,你可以考慮使用'HLists'而不是'Lists'。 [無形庫](https://github.com/milessabin/shapeless/)的實現有一個方法('filter'),它可以做你想做的。 [實施例](https://github.com/milessabin/shapeless/blob/master/core/src/test/scala/shapeless/hlist.scala#L604-L632)。 – folone

回答

5

TL; DR

使用此斯卡拉2.9.x:

def typeOnly[T](seq : Seq[Any])(implicit m : Manifest[T]) : Seq[T] = { 
    seq.collect { 
     case t if m.erasure.isInstance(t) => t.asInstanceOf[T] 
    } 
} 

scala> typeOnly[String](List(1,2,"3",4)) 
res1: Seq[String] = List(3)  

這對於Scala的2.10.x:

def typeOnly[T](seq : Seq[Any])(implicit tag : scala.reflect.ClassTag[T]) = { 
    seq.collect { 
     case t if tag.runtimeClass.isInstance(t) => t.asInstanceOf[T] 
    } 
} 

作爲Seq被定義爲trait Seq[+A] (關鍵是+),任何Seq[S]也是Seq[Any]。另一方面,正如已經說過的那樣,當函數被編譯時T被「忘記」了,所以你不能直接使用它。你必須通過T類作爲參數,如何。

def typeOnly[T](seq : Seq[Any], c : Class[T]) : Seq[T] = { 
    seq.flatMap { 
     case t if c.isInstance(t) => Some(t.asInstanceOf[T]) 
     case _ => None 
    } 
} 

在Scala中,APPART從Class[T],也有Manifest[T],這是更強大一點,作爲一個結果,更地道。特別是,它有一個方法erasure,它返回一個Class[T]。使用它,你可以去寫這樣的功能:

def typeOnly[T](seq : Seq[Any], m : Manifest[T]) : Seq[T] = { 
    seq.flatMap { 
     case t if m.erasure.isInstance(t) => Some(t.asInstanceOf[T]) 
     case _ => None 
    } 
} 

看來我們沒有獲得任何東西。但是,如果您很好地詢問編譯器(使用implicit),則在調用該函數時它會通過Manifest

def typeOnly[T](seq : Seq[Any])(implicit m : Manifest[T]) : Seq[T] = { 
    seq.flatMap { 
     case t if m.erasure.isInstance(t) => Some(t.asInstanceOf[T]) 
     case _ => None 
    } 
} 

例子:

scala> typeOnly[java.lang.Integer](List(1,2,"3",4)) 
res2: Seq[java.lang.Integer] = List(1, 2, 4) 

scala> typeOnly[String](List(1,2,"3",4)) 
res3: Seq[String] = List(3) 

scala> typeOnly[java.lang.Double](List(1,2,"3",4)) 
res4: Seq[java.lang.Double] = List() 

有更多的選擇,其中一些還更地道。你可以,例如, 使用collect與部分定義的函數:

def typeOnly[T](seq : Seq[Any])(implicit m : Manifest[T]) : Seq[T] = { 
    seq.collect { 
     case t if m.erasure.isInstance(t) => t.asInstanceOf[T] 
    } 
} 

警告:前者的例子合作在斯卡拉2.9.3及以下。如果您正在開發斯卡拉2.10.xManifest#erasure已被棄用。使用runtimeClass代替:

def typeOnly[T](seq : Seq[Any])(implicit m : Manifest[T]) = { 
    seq.collect { 
     case t if m.runtimeClass.isInstance(t) => t.asInstanceOf[T] 
    } 
} 

由於艙單很快就會過時過(見下面的評論),你應該考慮使用ClassTag。

def typeOnly[T](seq : Seq[Any])(implicit tag : scala.reflect.ClassTag[T]) = { 
    seq.collect { 
     case t if tag.runtimeClass.isInstance(t) => t.asInstanceOf[T] 
    } 
} 
+0

請注意,在Scala 2.10中,你應該比'Manifest'更喜歡'TypeTag'。計劃棄用「Manifest」。 http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html#typetags_and_manifests – gzm0

+0

很高興知道。修復。 – vlopez

2

case t: T => Some(t)不過濾由T類型的元素。你需要的是這樣的:你調用這樣

def typeOnly[T](seq: Seq[Any], clazz: Class[T] = classOf[Any]): Seq[T] = { 
    seq.flatMap{ 
     case t: T if clazz.isInstance(t) => Some(t) 
     case _ => None 
    } 
    } 

typeOnly(Seq(1, "2", 3)) 
typeOnly(Seq(1, "2", 3), classOf[String]) 
0

如果你瞄準允許typeOnly用戶只指定T類型參數,你可以把它定義這個方法:

def typeOnly[S](seq: Seq[S]) = new { 
    def apply[T]: Seq[T] = 
    seq.flatMap{ 
     case t: T => Some(t) 
     case _ => None 
    } 
} 

你將不得不這樣調用它

val l = typeOnly(List(1, "2", 3, "4"))[String] 

(請注意參數後面的類型參數,而不是typeOnly之後)。

這增加了一個編譯器警告,因爲它特別使用結構類型。如果你想避免添加import language.reflectiveCalls來解決這個問題,你可以依靠一箇中間類:

case class TypeOnlyFilter[S](seq: Seq[S]) { 
    def apply[T]: Seq[T] = 
    seq.flatMap{ 
     case t: T => Some(t) 
     case _ => None 
    } 
} 

def typeOnly[S](seq: Seq[S]) = 
    new TypeOnlyFilter(seq) 

注意,此實現仍然有編譯器警告有關選中抽象型圖案T(你原來的實現一樣) 。你只需要需要T類型ClassTag來解決這個問題,從而使最終的解決方案是這樣的:

import scala.reflect.ClassTag 

case class TypeOnlyFilter[S](seq: Seq[S]) { 
    def apply[T: ClassTag]: Seq[T] = 
    seq.flatMap{ 
     case t: T => Some(t) 
     case _ => None 
    } 
} 

def typeOnly[S](seq: Seq[S]) = 
    new TypeOnlyFilter(seq)