簡體   English   中英

使用TypeTag的Typesafe泛型(帶有安全強制轉換)

[英]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]]將成功,但返回不包含StringList 。)

我的方法是使用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:如何獲取所需TypeTagtoSet (一個選擇是將類型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將有typeTagTypesafeList (因而在檢查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 TypesafeListTypeTag 我們需要在該行中顯式添加(typeTag[TypesafeList[Int]]) (原因是new TypesafeList期望使用TypeTag[_ <: TypesafeList[Int]] ,並且編譯器不夠聰明,無法看到他可以構造TypeTag[TypesafeList[Int]] 。)

問題4:我們如何定義TypesafeList以便不需要顯式地提供TypeTag

最后,我對整個示例有一些疑問:

問題5:運行時(至少)有兩個不同的TypeTag類,分別是scala.reflect.runtime.universe.TypeTagscala.reflect.api.TypeTags#TypeTag 這里哪一個是正確的?

問題6:我正在比較TypeTags包含的類型(即typeTag.tpe )。 我無視鏡子。 也應該比較鏡子嗎? (換句話說,如果兩個類型標簽具有兼容的類型但具有不同的鏡像,那么它們是否可以兼容分配?)

問題7 :(可能與問題6有關。)如果相同的合格名稱的類型已由不同的類加載器加載,會發生什么情況? 在這種情況下,上面的代碼正確嗎? (據我所知,應該不可能將test.Test從類加載器1加載到test.Test從類加載器2加載。)

問題8( 回答 ): TypeTag在這里是正確的工具嗎? 還是應該直接用Type標記對象? (畢竟,我只比較cast的類型。)但是據我所見(從各種討論中), TypeTag提出來是為類型安全標記類問題的解決方案。 為什么? 還是TypeTag用於什么?

問題9:對此有何評論? 在運行時比較兩種類型(使用<:< )聽起來很昂貴……是否有替代方法? (我想過可能從TypeTagsBoolean的映射構造一個映射,該映射記住哪些類型是賦值兼容的。但是,這樣的查找會更快嗎?據我所知, 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類型。 也就是說,如果TX的超類型,則只能編寫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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM