2016-07-17 31 views
0

我想實現可以選擇進行類型安全演員(例如,x.cast[List[U]])的參數化類(如List[T])。使用類型標籤的Typesafe仿製藥(安全演員)

通過類型安全我的意思是如果類型在運行時不正確,則可能會拋出異常,但可以保證如果轉換成功,則結果值的類型爲List[U]。 (例如,asInstanceOf沒有做到這一點。List(1,2,3).asInstanceOf[List[String]]會成功,但返回List不包含String秒)

我的方法是代碼,必須支持與TypeTag鑄造的所有對象。具體來說,我會實施一個Typesafe特徵與方法cast[U],其中暗示TypeTag類型U和在運行時檢查是否類型是子類型。這是我設法拿出代碼:

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將有實例typeTagTypeTag[T]。然後在cast[U]中的測試只能成功,如果T確實是U的子類型(否則,cast的隱含參數不存在)。

我們可以按以下步驟實現這個特點(這是設置一個簡單的包裝類):

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]]。我不知道這是可能的。

問題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]] { 
} 

在這裏,我們有「不小心」在Typesafe性狀的參數使用TypesafeList。這編譯好,但這意味着現在TypesafeOption將有typeTagTypesafeList!(因此,cast中的檢查將不正確,並且可能會發生錯誤轉換。)我相信這樣的混音可以很容易地發生,如果編譯器能夠捕捉到這種混音,那就太好了。 (在一定程度上,該類型約束T <: Typesafe[T]已經避免這種mixups(以下this),但遺憾的是沒有一個在TypesafeOption。)

問題3(answered):我們可以細化特徵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 s?

最後,我有一個關於整體例如一些問題:

問題5:有(至少)兩個不同的TypeTag班在運行時,即scala.reflect.runtime.universe.TypeTagscala.reflect.api.TypeTags#TypeTag。哪一個在這裏是正確的?

問題6:我正在比較TypeTags(即typeTag.tpe)中包含的類型。我忽略了鏡子。是否應該對鏡子進行比較? (換句話說,如果兩個類型的變量具有兼容的類型,但不同的鏡子,他們將分配兼容?)

問題7:(可能涉及到問題6)會發生什麼,如果類型相同的限定名稱是否已被不同的類加載器加載?上面的代碼在這種情況下是否正確? (即,它不應該是可能的投test.Test從類加載器加載1,從類加載器加載2 test.Test,據我瞭解。)

問題8(answered):TypeTag這裏的合適的儀器?或者我應該直接用Type s來標記對象? (畢竟,我僅在cast中比較了這些類型)。但據我所知(從各種討論中),TypeTag s是作爲類型安全類標記問題的解決方案提供的。爲什麼?或者TypeTag是什麼?

問題9:對此表現有何評論?在運行時比較兩種類型(使用<:<)聽起來可能很昂貴...是否有其他選擇? (我想可能是從TypeTags -pairs到Boolean這樣的地圖來記錄哪些類型是分配兼容的,但是這樣的查找速度會更快嗎?TypeTags沒有用於快速查找的唯一ID,據我所知。 (GHC爲此使用「指紋」,我想。))

問題10:還有其他意見嗎?我做錯了什麼?我的結論是cast是類型安全正確的嗎?

+0

LT;博士。你的第一部分看起來像是https://github.com/milessabin/shapeless。 – pedrofurla

+0

更具體地說:https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala –

+0

shapeless/Typeable有一個類似的目標,但是有兩個重要的區別: (a)通過遞歸遍歷數據結構(例如,在list.cast(List [Int])中檢查cast是否安全),它將遍歷列表中的所有元素並檢查它們是否是整數。) (b)需要爲任何可能用作類型參數的類型定義一個Typeable實例。 (即,對於'list.cast(List [Bla])',我們需要一個Typeable [Bla]在範圍內。)這使得它更難作爲一個庫來使用(因爲用戶必須提供Typeable-instances爲他所有的課程。 –

回答

0

編輯:這種解決方案是錯誤的。使用此解決方案時,仍然可以使用val typeTag = typeTag[Nothing],然後將該類轉換爲任何類型。

回答問題3我們可以細化特徵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

0

回答問題8(是TypeTag合適的儀器在這裏還是應該我寧願對象直接與類型的標籤?)

一個原因是,TypeTag有代表一個類型參數標記的類型。也就是說,TypeTag[T]靜態強制我們有TypeTagT。如果我們使用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需要有一個鏡在它除了類型。)