2012-07-15 184 views
9

我感興趣的是手動創建TypeTag(因爲2.10M5):如何手動創建TypeTag?

object X { 
    import reflect.runtime.universe._ 
    def tt[A : TypeTag](a: A) = typeTag[A] // how to do this manually? 
    val t = tt(List("")(_)) 
} 

scalac -Xprint:typer <file>.scala結果

package <empty> { 
    object X extends scala.AnyRef { 
    def <init>(): X.type = { 
     X.super.<init>(); 
    () 
    }; 
    import scala.reflect.runtime.`package`.universe._; 
    def tt[A >: Nothing <: Any](a: A)(implicit evidence$1: reflect.runtime.universe.TypeTag[A]): reflect.runtime.universe.TypeTag[A] = scala.reflect.runtime.`package`.universe.typeTag[A](evidence$1); 
    private[this] val t: reflect.runtime.universe.TypeTag[Int => String] = X.this.tt[Int => String](((x$1: Int) => immutable.this.List.apply[String]("").apply(x$1)))({ 
     val $u: reflect.runtime.universe.type = scala.this.reflect.runtime.`package`.universe; 
     val $m: $u.Mirror = scala.this.reflect.runtime.`package`.universe.runtimeMirror(this.getClass().getClassLoader()); 
     $u.TypeTag.apply[Int => String]($m, { 
     final class $typecreator1 extends TypeCreator { 
      def <init>(): $typecreator1 = { 
      $typecreator1.super.<init>(); 
      () 
      }; 
      def apply[U >: Nothing <: scala.reflect.base.Universe with Singleton]($m$untyped: scala.reflect.base.MirrorOf[U]): U#Type = { 
      val $u: U = $m$untyped.universe; 
      val $m: $u.Mirror = $m$untyped.asInstanceOf[$u.Mirror]; 
      $u.TypeRef.apply($u.ThisType.apply($m.staticModule("scala").asModuleSymbol.moduleClass), $m.staticClass("scala.Function1"), scala.collection.immutable.List.apply[$u.Type]($m.staticClass("scala.Int").asTypeSymbol.asTypeConstructor, $m.staticClass("java.lang.String").asTypeSymbol.asTypeConstructor)) 
      } 
     }; 
     new $typecreator1() 
     }) 
    }); 
    <stable> <accessor> def t: reflect.runtime.universe.TypeTag[Int => String] = X.this.t 
    } 
} 

這似乎是完全編譯魔法,因爲類型是固定的。不過有沒有辦法做到這一點手動?

回答

10

在M3中,您可以使用一種非常簡單的方式創建類型標籤,例如:TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil))。它基本上意味着一旦創建了一個類型標籤,它就永遠被綁定到某個類加載器(也就是上面例子中用於加載scala符號的類)。

那時很好,因爲我們認爲我們可以有一個適合所有類加載器的萬能鏡。這很方便,因爲您只需編寫implicitly[TypeTag[T]],編譯器將使用該全局鏡像實例化您請求的類型。

不幸的是,後來,根據反饋意見,我們意識到這不會起作用,並且我們需要多個鏡像 - 每個鏡像都有自己的類加載器。

然後很明顯,類型標籤需要靈活,因爲一旦你編寫implicitly[TypeTag[T]]編譯器沒有足夠的信息要使用什麼類加載器。基本上有兩種選擇:1)使類型標記路徑依賴於鏡像(這樣每次從編譯器請求類型標記時,都會強制顯式提供鏡像),2)將類型標記轉換爲類型工廠,在任何鏡像中實例化自己。長話短說,第一種選擇不起作用,所以我們就是我們所在的地方。

因此,目前類型標籤需要以相當迂迴的方式創建,如https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143中所述。您可以調用在伴侶TypeTag中定義的工廠方法,並提供兩個參數:1)默認鏡像,其中類型標記將被實例化; 2)類型工廠,可以在任意鏡像中實例化類型標記。這基本上是編譯器在上面附加的打印輸出中做的。

爲什麼我稱這個環島?因爲要提供類型工廠,您需要手動對scala.reflect.base.TypeFactory類進行子類化。這是Scala類型系統在函數和依賴類型方法之間的邊界的一個不幸的限制。

+0

我看到,Scala Reflection比我目前能想到的要複雜得多。我仍然需要花費很多時間,直到得到正在發生的事情... – sschaef 2012-07-16 12:50:05

+0

你能否詳細說明你的用例?動態構建類型標記(以及因此的Scala類型),特別是從Java中構建類型標記,看起來非常硬。也許類標籤(只攜帶Java類,而不是Scala類型)就足夠了? – 2012-07-16 14:44:50

+0

我沒有用例;)我想知道事情是如何工作的,以及如何使用它/用於以後使用... – sschaef 2012-07-16 14:51:17

5

函數定義

def tt[A : TypeTag](a: A) = typeTag[A] 

是寫

def tt(a: A)(implicit tag: TypeTag[A]) = tag 

的只是另一種方式,這意味着正由編譯器隱式創建一個標籤的實例。這背後的原因是Scala編譯器通過硬編碼擦除​​類型信息來解決JVM的類型擦除問題。

如果您不熟悉類型擦除問題,那就是JVM不存儲類型參數信息,例如,類型Seq[Set[Int]]將被JVM看作Seq[_],並且將無法你可以通過反射找出遺漏的類型信息。

+0

我不認爲這個答案是完全正確的,因爲有一種方法可以手動創建艙單這是由編譯器自動創建的,太:'新的艙單[INT] {高清擦除= classOf [INT]} '(這在2.10中不起作用) – sschaef 2012-07-15 21:36:12

+1

我不確定我是否理解你要在這裏實現的目標。當然,你可以手動實例化一個TypeTag類,但它的唯一目的是在編譯器後面實例化,這會生成與案例相關的信息。如果無論你試圖模仿它的目的是什麼,那麼你的反編譯的'private [this] val t:reflect.runtime.universe.TypeTag'聲明就完全顯示了你如何做到這一點,但是這不是這個類的目的使用。 – 2012-07-15 21:51:39

+0

用於此的用例可能是使用Scala方法,該方法需要Java端的TypeTag。我想:也許有比編譯器更容易/另一種方式。 – sschaef 2012-07-15 22:19:34

8

基於Get TypeTag[A] from Class[A]

import scala.reflect.runtime.universe._ 

def typeToTypeTag[T](
    tpe: Type, 
    mirror: reflect.api.Mirror[reflect.runtime.universe.type] 
): TypeTag[T] = { 
    TypeTag(mirror, new reflect.api.TypeCreator { 
    def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = { 
     assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.") 
     tpe.asInstanceOf[U#Type] 
    } 
    }) 
} 

例如,這可以用來獲得TypeTag的另一TypeTag部分:

def inside[A, B](tag: TypeTag[(A, B)]): (TypeTag[A], TypeTag[B]) = { 
    val tpes = tag.tpe.asInstanceOf[TypeRefApi].args 
    val tagA = typeToTypeTag[A](tpes(0), tag.mirror) 
    val tagB = typeToTypeTag[B](tpes(1), tag.mirror) 
    return (tagA, tagB) 
} 

這個作品在斯卡拉2.10.2:

scala> inside(typeTag[(Int, Double)]) 
res0: (reflect.runtime.universe.TypeTag[Int], reflect.runtime.universe.TypeTag[Double]) = (TypeTag[Int],TypeTag[Double]) 

與特定綁定的限制可能不是問題,只要您沒有多個ClassLoader即可。

+1

這正是我需要的。 – 2014-11-13 00:25:34

+1

此方法失去了標記被序列化的能力:java.io.NotSerializableException:scala.reflect.runtime.JavaMirrors $ JavaMirror – user48956 2016-08-23 01:58:42

+0

我在Scala 2.10上,所以我從來沒有看到可序列化的TypeTags。一定很棒! :) – 2016-08-23 09:32:16

1

目前,有三種方式可以手動創建支持泛型的TypeTag。前兩個依賴於scala-compiler,並與他們你的類型檢查,就像在編譯的時候,因爲它是一個實際的代碼編譯

import scala.reflect.runtime.universe._ 
import scala.reflect.runtime.currentMirror 
import scala.tools.reflect.ToolBox 

val toolbox = currentMirror.mkToolBox() 

def createTypeTag(tp: String): TypeTag[_] = { 
    val ttree = toolbox.parse(s"scala.reflect.runtime.universe.typeTag[$tp]") 
    toolbox.eval(ttree).asInstanceOf[TypeTag[_]] 
} 

如果你需要一個序列化TypeTag和性能是不是你主要關心的是,第一種方法是最簡潔的。 OTOH,如果您的用例需要非常高效,請注意即時編譯可能需要幾秒鐘才能完成。

第二種方法執行好一點:

def createTypeTag(tp: String): TypeTag[_] = { 
    val ttagCall = s"scala.reflect.runtime.universe.typeTag[$tp]" 
    val tpe = toolbox.typecheck(toolbox.parse(ttagCall), toolbox.TYPEmode).tpe.resultType.typeArgs.head 

    TypeTag(currentMirror, new reflect.api.TypeCreator { 
    def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = { 
     assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.") 
     tpe.asInstanceOf[U#Type] 
    } 
    } 
} 

這第二個模式下創建一個類型樹並獲取標誌着TypeTag,也就是說,如果你的目標是List[String],它會被轉換爲具體的類型scala.collection.immutable.List[String],因爲scala.List只是一個類型別名。這實際上是從typeTag[List[String]]預期的行爲。

最後一種方法是手動創建Type並使用它創建TypeTag。這種方法容易出錯,只有有限的類型檢查,並且使用內部API。然而,這是手動獲取TypeTag的最快方法。

def createTypeTag(tp: String): TypeTag[_] = { 
    val typs = // ... manipulate the string to extract type and parameters 
    val typSym = currentMirror.staticClass(typs[0]) 
    val paramSym = currentMirror.staticClass(typs[1]) 

    val tpe = universe.internal.typeRef(NoPrefix, typSym, List(paramSym.selfType)) 

    val ttag = TypeTag(currentMirror, new TypeCreator { 
    override def apply[U <: Universe with Singleton](m: Mirror[U]): U#Type = { 
     assert(m == currentMirror, s"TypeTag[$tpe] defined in $currentMirror cannot be migrated to $m.") 
     tpe.asInstanceOf[U#Type] 
    } 
    }) 
}