![](/img/trans.png)
[英]Scala compiler says “No TypeTag available for T” in method using generics
[英]Typesafe generics using TypeTag (with safe cast)
我想實現參數類(例如List[T]
), x.cast[List[U]]
可以選擇進行類型安全的轉換(例如x.cast[List[U]]
)。
通過類型安全,我的意思是如果類型在運行時不正確,則強制類型轉換可能引發異常,但是可以保證,如果強制類型轉換成功,則結果值將為List[U]
類型。 (例如, asInstanceOf
不會執行此操作List(1,2,3).asInstanceOf[List[String]]
將成功,但返回不包含String
的List
。)
我的方法是使用TypeTag
標記所有應支持轉換的TypeTag
。 具體來說,我將使用方法cast[U]
實現一個特征Typesafe
,該方法期望類型U
的隱式TypeTag
並在運行時檢查類型是否為子類型。 這是我設法提供的代碼:
import scala.reflect.runtime.universe.TypeTag
trait Typesafe[+T <: Typesafe[T]] {
val typeTag: TypeTag[_ <: T]
def cast[U](implicit typeTag: TypeTag[U]) = {
if (this.typeTag.tpe <:< typeTag.tpe)
this.asInstanceOf[U]
else
throw new ClassCastException(s"Cannot cast ${this.typeTag} to ${typeTag}")
}
}
邏輯是:繼承Typesafe[T]
類T
必須使用TypeTag[T]
實例化typeTag
。 然后,僅當T
確實是U
的子類型時, cast[U]
的測試才能成功(否則, cast
的隱式參數不存在)。
我們可以按以下方式實現此特征(這是Set的一個簡單包裝器類):
class TypesafeSet[T](val set : Set[T])
(implicit val typeTag:TypeTag[_<:TypesafeSet[T]])
extends Typesafe[TypesafeSet[T]] {
}
子類型化有效,但是不幸的是,我們每次都需要指定extends Typesafe[...]
子句。
import scala.collection.immutable.ListSet
class TypesafeListSet[T](set: ListSet[T])
(implicit override val typeTag:TypeTag[_<:TypesafeListSet[T]])
extends TypesafeSet[T](set) with Typesafe[TypesafeListSet[T]] {
}
問題1:我們可以改進這種模式,以便不必重復extends Typesafe[...]
子句嗎? (目前,如果我們不重復, TypesafeListSet[T]
不能被轉換為TypesafeListSet[T]
但是,在下面的示例中,我們遇到了問題:
class TypesafeList[T](val list : List[T])
(implicit val typeTag:TypeTag[_<:TypesafeList[T]])
extends Typesafe[TypesafeList[T]] {
val self = this
def toSet : TypesafeSet[T] = new TypesafeListSet(ListSet(list : _*))
}
方法toSet
無法編譯,因為編譯器無法為new TypesafeListSet
解析隱式TypeTag[TypesafeListSet[T]]
。 需要從typeTag提取TypeTag[T]
,然后TypeTag[TypesafeListSet[T]]
重建TypeTag[TypesafeListSet[T]]
。 我不知道這怎么可能。
問題2:如何獲取所需TypeTag
在toSet
? (一個選擇是將類型TypeTag[TypesafeListSet[T]]
的隱式參數添加到toSet
,但這會向外推問題,並泄漏實現細節,即toSet
使用ListSet
。)
最后,可以編寫以下代碼,違反類型安全性:
class TypesafeOption[T](val option : Option[T])
(implicit val typeTag:TypeTag[_<:TypesafeList[T]])
extends Typesafe[TypesafeList[T]] {
}
在這里,我們用“不小心” TypesafeList
中的參數Typesafe
特性。 編譯沒有問題,但它意味着現在TypesafeOption
將有typeTag
的TypesafeList
! (因而在檢查cast
將是不正確的,錯誤的類型轉換可能會發生。)我認為,這種mixups可以很容易地發生,這將是很好的,如果他們可以通過編譯器捕獲。 (在一定程度上,所述類型約束T <: Typesafe[T]
已經避免這種mixups(以下此 ),但不幸的是不是一個在TypesafeOption
。)
問題3(已回答 ):我們是否可以改進特征Typesafe
的定義,以致無法以某種方式實例化Typesafe
從而使cast
行為不正確?
最后,幾行代碼應如何使用這些類:
import scala.reflect.runtime.universe.typeTag
object Test {
def main(args:Array[String]) = {
val list = new TypesafeList(List(1,2,3))
val set = list.toSet
val listSet : TypesafeListSet[Int] = set.cast[TypesafeListSet[Int]]
}
}
不幸的是,該代碼無法編譯。 編譯器找不到用於調用new TypesafeList
的TypeTag
。 我們需要在該行中顯式添加(typeTag[TypesafeList[Int]])
! (原因是new TypesafeList
期望使用TypeTag[_ <: TypesafeList[Int]]
,並且編譯器不夠聰明,無法看到他可以構造TypeTag[TypesafeList[Int]]
。)
問題4:我們如何定義TypesafeList
以便不需要顯式地提供TypeTag
?
最后,我對整個示例有一些疑問:
問題5:運行時(至少)有兩個不同的TypeTag
類,分別是scala.reflect.runtime.universe.TypeTag
和scala.reflect.api.TypeTags#TypeTag
。 這里哪一個是正確的?
問題6:我正在比較TypeTags
包含的類型(即typeTag.tpe
)。 我無視鏡子。 也應該比較鏡子嗎? (換句話說,如果兩個類型標簽具有兼容的類型但具有不同的鏡像,那么它們是否可以兼容分配?)
問題7 :(可能與問題6有關。)如果相同的合格名稱的類型已由不同的類加載器加載,會發生什么情況? 在這種情況下,上面的代碼正確嗎? (據我所知,應該不可能將test.Test
從類加載器1加載到test.Test
從類加載器2加載。)
問題8( 回答 ): TypeTag
在這里是正確的工具嗎? 還是應該直接用Type
標記對象? (畢竟,我只比較cast
的類型。)但是據我所見(從各種討論中), TypeTag
提出來是為類型安全標記類問題的解決方案。 為什么? 還是TypeTag
用於什么?
問題9:對此有何評論? 在運行時比較兩種類型(使用<:<
)聽起來很昂貴……是否有替代方法? (我想過可能從TypeTags
到Boolean
的映射構造一個映射,該映射記住哪些類型是賦值兼容的。但是,這樣的查找會更快嗎?據我所知, TypeTag
沒有用於快速查找的唯一ID。 (我認為,GHC為此使用了“指紋”。)
問題10:還有其他意見嗎? 我做錯了什么? 我的結論是, cast
是類型安全的正確嗎?
編輯:此解決方案是錯誤的。 使用此解決方案時,仍然可以使用val typeTag = typeTag[Nothing]
,然后將該類轉換為任何類型。
對問題3的答案 ( 我們是否可以改進特征Typesafe
的定義,以致無法以某種方式實例化Typesafe
從而Typesafe
行為不正確? )
這可以通過指定“自我類型”(請參見spec )來完成。 在特征中,可以指定繼承該類的類應具有的類型T
這是通過在特征定義的開頭編寫this:T =>
來完成的。 在我們的特定情況下:
trait Typesafe[+T <: Typesafe[T]] {
this : T =>
val typeTag: TypeTag[_ <: T]
def cast[U](implicit typeTag: TypeTag[U]) = {
if (this.typeTag.tpe <:< typeTag.tpe)
this.asInstanceOf[U]
else
throw new ClassCastException(s"Cannot cast ${this.typeTag} to ${typeTag}")
}
}
現在,任何擴展Typesafe[T]
必須為T
類型。 也就是說,如果T
是X
的超類型,則只能編寫class X extends Typesafe[T]
,因此將保證提供給Typesafe
的類型標記是X
的超類型。
問題8的答案 (TypeTag是這里的正確工具嗎?還是應該直接用Types標記對象?) :
一個原因是TypeTag
具有一個類型參數,該參數表示被標記的類型。 也就是說, TypeTag[T]
靜態地強制我們為T
提供一個TypeTag
。 如果我們改用Type
,則不會強制Typesafe.typeTag
值是正確類型的標簽。
例:
trait Typesafe[+T <: Typesafe[T]] {
val typ: Type
[...]
}
現在可以寫:
class TypesafeSet[T](val set : Set[T])
extends Typesafe[TypesafeSet[T]] {
val typ = typeOf[String] // !!!
}
沒有TypeTag
似乎無法避免這種情況。
(但是,這不能解釋為什么TypeTag
除了類型之外還需要具有一個鏡像)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.