Learning Manifest, ClassTag, TypeTag in Scala

Keywords: Scala Java Spark Hadoop

Introduction to Manifest

Manifest is a feature introduced by Scala 2.8 that allows compilers to obtain generic type information at runtime.
stay JVM On the other hand, the generic parameter type T is "wiped" off at runtime, and the compiler treats T as an Object.
So the specific information of T is not available; in order to get the information of T at runtime,
scala requires additional Manifest to store T information and use it as a parameter in the runtime context of the method

def test[T] (x:T, m:Manifest[T]) { ... }
With Manifest[T], a parameter m that records information of type T, T can be judged more accurately at run time according to M.
But if each method is written like this, it would be very unfriendly for the caller of the method to pass in an additional m parameter, and the design of the method would be a scar.
Fortunately, there is implicit conversion and parameter function in scala, where implicit parameters can be used to alleviate the caller's trouble.

An example is given here from StackOverflow:
def foo[T](x: List[T])(implicit m: Manifest[T]) = {
    if (m <:< manifest[String])
        println("Hey, this list is full of strings")
    else
        println("Non-stringy list")
}
foo(List("one", "two")) // Hey, this list is full of strings
foo(List(1, 2)) // Non-stringy list
foo(List("one", 2)) // Non-stringy list
The implicit parameter m is automatically passed in by the compiler according to the context. For example, the compiler infers that the type of T is String according to "one" and "two".
Thus, an object parameter of Manifest[String] type is implicitly passed in, so that the runtime can do more things based on this parameter.

However, the above definition of foo method using implicit parameters is still verbose, so "context binding" is introduced in scala.
Review the previous context bounds to make the foo method
def foo[T](x: List[T]) (implicit m: Manifest[T])
It can be simplified as follows:
def foo[T:Manifest] (x: List[T])
This mechanism was introduced by the redesign of arrays by scala 2.8, originally to solve the problem of arrays.

The difference between Manifest and lass Manifest

Follow-up is used in more ways. When Manifest was introduced, a weaker Cass Manifest was introduced.
The so-called weakness refers to the fact that type information is not as complete as Manifest, mainly for higher-order types:
scala> class A[T]
scala> val m = manifest[A[String]]
scala> val cm = classManifest[A[String]]
According to the specification, m's information is complete: m: Manifest [A [String] = A [java. lang. String],
But cm only has A [] which does not contain information about type parameters.
But I also validated cm at 2.10: cm: ClassManifest [A [String] = A [java. lang. String]

It is also included when obtaining type parameters of a type:
scala> m.typeArguments
res8: List[scala.reflect.Manifest[_]] = List(java.lang.String)
scala> cm.typeArguments
res9: List[scala.reflect.OptManifest[_]] = List(java.lang.String)
The cases are as follows:
Scala > class A [B]//A[+B] A[-B] has the same effect
defined class A
scala> manifest[A[_]]
res15: scala.reflect.Manifest[A[_]] = A[_ <: Any]
scala> classManifest[A[_]]
res16: scala.reflect.ClassTag[A[_]] = A[<?>]
Here we basically understand the difference between Manifest and lass Manifest.

Problems in Manifest


But in 2.10, scala replaced Manifest with TypeTag and ClassManifest with ClassTag.
The reason is that Manifest has problems with path dependency types:
scala> class Foo{class Bar}
scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev

scala> val f1 = new Foo;val b1 = new f1.Bar
scala> val f2 = new Foo;val b2 = new f2.Bar


scala> val ev1 = m(f1)(b1)
ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar

scala> val ev2 = m(f2)(b2)
ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar

scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true
ev1 should not be equal to ev2, because its dependent path (external instance) is different. Replace Manifest with TypeTag, and the result is correct, requiring import scala.reflect.runtime.universe._

Manifest->TypeTag ClassTag->ClassManifest


There are other factors (see the quotation below), so in Version 2.10, Manifest was replaced by TypeTag.
Manifests are a lie. It has no knowledge of variance (assumes all type parameters are co-variants),
and it has no support for path-dependent, existential or structural types.

TypeTags are types as the compiler understands them. Not "like" the compiler understands them,
but "as" the compiler understands them — the compiler itself use TypeTags. It's not 1-to-1, it's just 1. :-)

Code example

​/**
 * A `ClassTag[T]` stores the erased class of a given type `T`, accessible via the `runtimeClass`
 * field. This is particularly useful for instantiating `Array`s whose element types are unknown
 * at compile time.
 * When a generic object runs, its T is erased. ClassTag[T] stores a given type of T, which we access through runtimeClass
 * This generic type specifies the object at runtime. This is particularly useful in instantiating Array. An array is being built but its element type is unknown
 * When. Element types of arrays are unknown at compile time (runtime).
 * `ClassTag`s are a weaker special case of [[scala.reflect.api.TypeTags#TypeTag]]s, in that they
 * wrap only the runtime class of a given type, whereas a `TypeTag` contains all static type
 * information. That is, `ClassTag`s are constructed from knowing only the top-level class of a
 * type, without necessarily knowing all of its argument types. This runtime information is enough
 * for runtime `Array` creation.
 * ClassTag It's a weaker case than TypeTag. ClassTag contains only the class information given at runtime. TypeTag contains not only classes
 * Category information of ______________ We use ClassTag in most cases, because ClassTag tells us to ship
 * The actual type at line time is enough for us to use in generics.
 *
 * The array itself is generic. And if we want to create a generic array, it's theoretically impossible.
 * When running in Scala, arrays must have specific types. If you continue to be generic, you will be prompted.
 * There is no specific type and the corresponding array can not be created. This is a big problem!
 * ClassTag was introduced in Scala. With it, we can create a generic array.
 *
 * We're going to build generic objects, here's the generic array. We need ClassTag to help us store the actual type of T.
 * At runtime, we can get the actual type.
 * */

import scala.reflect.ClassTag
import scala.reflect.runtime.universe._
class MyType[T]

object SimpleDemo {
  def arrayMake[T: ClassTag](first: T, second: T) = {
    val r = new Array[T](2)
    r(0) = first
    r(1) = second
    r
  }
  //The above is equivalent to the following. The following is a more native form of writing. The following wording is not recommended
  def arrayMake2[T](first: T, second: T)(implicit m:  ClassTag[T]) = {
    println(m.typeArguments)
    //Actual types of print generics
    println(implicitly[ClassTag[T]].runtimeClass)

    val r = new Array[T](2)
    r(0) = first
    r(1) = second
    r
  }

  //implicit m: ClassTag[T] to implicit m: Manifest[T] is also possible
  def manif[T](x: List[T])(implicit m: Manifest[T]) = {
    if (m <:< manifest[String])
      println("List strings")
    else
      println("Some other type")
  }

  def manif2[T](x: List[T])(implicit m: ClassManifest[T]) = {
    //classManifest has a weaker ability to get information than manifest.
    if (m <:< classManifest[String])
      println("List strings")
    else
      println("Some other type")
  }
  
  //implicit m: ClassTag[T] to implicit m: TypeTag[T] is also possible
  def manif3[T](x: List[T])(implicit m: TypeTag[T]) = {
    println(x)
  }

  //ClassTag is our most commonly used. It mainly specifies at runtime information of categories that cannot be determined at compile time.
  // The underscore in my Array[T](elems: *) is a placeholder, representing many elements.
  def mkArray[T: ClassTag](elems: T*) = Array[T](elems: _*)

  
  def main(args: Array[String]) {
    arrayMake(1, 2).foreach(println)
    arrayMake2(1, 2).foreach(println)
    manif(List("Spark", "Hadoop"))
    manif(List(1, 2))
    manif(List("Scala", 3))
    manif2(List("Spark", "Hadoop"))
    manif2(List(1, 2))
    manif2(List("Scala", 3))
    
    manif3(List("Spark", "Hadoop"))
    manif3(List(1, 2))
    manif3(List("Scala", 3))

    val m = manifest[MyType[String]]
    println(m) //myscala.scalaexercises.classtag.MyType[java.lang.String]
    val cm = classManifest[MyType[String]]
    println(cm) //myscala.scalaexercises.classtag.MyType[java.lang.String]

    /*
    In fact, manifest is problematic. manifest misjudges actual types (such as dependency paths), so it later introduced ClassTag and TypeTag.
    Replace manifest with TypeTag and classManifest with ClassTag
    */
    mkArray(1, 2, 3, 4, 5).foreach(println)
  }
}

Posted by SheepWoolie on Sun, 14 Apr 2019 16:21:32 -0700