2016-04-06 57 views
3

我正在使用Scala 2.10,並且發現在使用泛型參數的類型推斷時我認爲是奇怪的行爲。考慮下面的例子(注意i僅僅是一個虛擬變量):Scala通用類型推斷與ClassTag不一致

scala> import scala.reflect.{ClassTag,classTag} 
    | 
    | def broken[T : ClassTag](i : Int) : List[T] = { 
    | println("ClassTag is : " + classTag[T]) 
    | List.empty[T] 
    | } 

import scala.reflect.{ClassTag, classTag} 

broken: [T](i: Int)(implicit evidence$1: scala.reflect.ClassTag[T])List[T] 

scala> broken(3) 
ClassTag is : Nothing 
res0: List[Nothing] = List() 

scala> val brokenVal : List[String] = broken(3) 
ClassTag is : Nothing 
brokenVal: List[String] = List() 

呼叫broken(3)似乎一致,在打印語句中的功能與推斷ClassTag同意。

但是,第二種說法是,編譯器推斷ClassTag,並在函數內部打印的內容與實際返回類型是什麼不一致。

我本來希望編譯器能從類型聲明中推出ClassTag,即List[String]。這是不正確的?有人可以啓發我嗎?

這裏的某些其它方面:我實際使用classtag在一些代碼(這裏沒有顯示)來篩選集合,這顯然失敗的時候我不指定T明確,因爲它是比較反對Nothing類型。

回答

1

實際的返回你的第二個例子的類型是List[Nothing],所以ClassTag與它完全一致。發生這種情況是因爲List是協變的(定義爲List[+A]而不是List[A])。此協方差允許您編寫像list = Nil(因爲Nil.type = List[Nothing])這樣的語句,但它也允許編譯器將底部類型推斷爲列表類型參數,因爲List[Nothing]也是List[String]的子類型,同樣由於它的協方差。所以實際的類型不匹配在返回類型和brokenVal的類型之間。

爲了克服這個限制,你可以使用一個不變的類型,你的返回類型,即:

import scala.reflect.ClassTag 

class Invariant[T] 

def f[T: ClassTag](i: Int): Invariant[T] = { 
    println("ClassTag: " + implicitly[ClassTag[T]]) 
    new Invariant[T] 
} 

val ret: Invariant[String] = f(3) 
// ClassTag: java.lang.String 
// ret: Invariant[String] = [email protected] 

在另一邊,你可能讓編譯器從別的推斷T,例如,通過傳遞作爲參數的類型爲T的值。你不能讓這個例子在香草List上工作,因爲你不能改變它們的方差。

+0

我實際上使用rx Observables,它也是協變的,並且遇到了這個問題。我在'collect'語句中使用ClassTag只拾取某種類型的項目。因爲我使用類型本身來過濾東西,即'observable collect {case t:T => t}',所以沒有任何價值可以作爲參數傳入,像你說的那樣可以解決推理問題。感謝您的反饋意見。 – Luciano

+0

即使在這種情況下,仍然有可能收集類型簽名的宏解決方案,但我認爲在這裏使用宏是矯枉過正的。只需省略變量類型並明確指定'[T]'。 – Maxim