Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit ClassTag in pattern matching

Let's take this for an example:

import scala.reflect._

def get[T](list: List[Any])(implicit tag: ClassTag[T]) = {
  list.flatMap {
    case element: T => Some(element)
    case _ => None
  }
}

I can use get() to get values of type T from a list (e.g. get[String](list) will give me all strings from that list).

Now, I understand that compiler provides the value of type ClassTag[String] automatically. I also understand that ClassTag is a type class, and somewhere behind the curtain there's a piece of code that says implicitly[ClassTag[T]].getRuntimeClass() or whatever.

But if that's so, how come we can pattern match without the class tag (we just can't differentiate between erased types in that case)? I mean, how is it achieved that, if I declare an implicit parameter (which is automatically provided by the compiler), I get one behavior, but if i don't I get a different behavior?

like image 917
slouc Avatar asked Apr 26 '16 20:04

slouc


People also ask

What is ClassTag in Scala?

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. ClassTag s are a weaker special case of scala. reflect. api.

What is Scala type erasure?

Type erasure refers to the runtime encoding of parameterized classes in Scala. It is simply performed by Scala compiler in which it removes all the generic type information after compilation. In Scala, generics are erased at runtime, which means that the runtime type of List[Int] and List[Boolean] is actually the same.


1 Answers

The compiler automatically translates your code roughly to this:

  def get[T](list: List[Any])(implicit tag: ClassTag[T]) = list.flatMap {
    case (element @ tag(_: T)) => Some(element)
    case _ => None
  }

ClassTag has an unapply(x: Any) overload that allows it to pattern match on values. I've cleaned up the tree obtained from reify to only show the relevant parts, but this will show you the full tree:

scala.reflect.runtime.universe.reify { 
    def get[T](list: List[Any])(implicit tag: ClassTag[T]) = {
      list.flatMap {
        case element: T => Some(element)
        case _ => None
      }
    }
}

Also see the scaladoc:

The compiler tries to turn unchecked type tests in pattern matches into checked ones by wrapping a (_: T) type pattern as ct(_: T), where ct is the ClassTag[T] instance. Type tests necessary before calling other extractors are treated similarly. SomeExtractor(...) is turned into ct(SomeExtractor(...)) if T in SomeExtractor.unapply(x: T) is uncheckable, but we have an instance of ClassTag[T].

like image 145
Michael Zajac Avatar answered Oct 17 '22 02:10

Michael Zajac