Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala getting field and type of field of a case class

So I'm trying to get the field and their types in a case class. At the moment I am doing it like so

typeOf[CaseClass].members.filter(!_.isMethod).foreach{
   x =>
     x.typeSignature match {
        case _:TypeOfFieldInCaseClass => do something
        case _:AnotherTypeOfFieldInCaseClass => do something
     }
}

the problem is x.typeSignature is of type reflect.runtime.universe.Type which cannot match on any of the types that reside in the case class. Is there some way to do this?

like image 945
Justin Juntang Avatar asked Oct 22 '14 04:10

Justin Juntang


1 Answers

Let's say you have the following case class defined:

case class CaseClass(i: Int, s: String)

with pattern matching you can achieve what you want with the following:

import scala.reflect.runtime.universe._

typeOf[CaseClass].members.filter(!_.isMethod).map(_.typeSignature).foreach {
  case t if t == typeOf[Int] => print("i")
  case s if s == typeOf[String] => print("s")
}

Why?

So why the first attempt does not work?

That's because in your code you are using type-patterns. Type patterns check the type of subject-to-match - which is a type signature in this case - at runtime. So by using _: Int we are asking for a type check at runtime against each type signature of non-method members of CaseClass.

But in this case what we need is a value match.

Let's take a closer look (using Scala REPL):

scala> case class CaseClass(i: Int, s: String)
defined class CaseClass

scala> typeOf[CaseClass]
res1: reflect.runtime.universe.Type = CaseClass

scala> typeOf[CaseClass].members
res2: reflect.runtime.universe.MemberScope = Scopes(method equals, method toString, ..)

scala> typeOf[CaseClass].members.filter(!_.isMethod)
res4: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(value s, value i)

scala> typeOf[CaseClass].members.filter(!_.isMethod).map(_.typeSignature)
res5: Iterable[reflect.runtime.universe.Type] = List(String, scala.Int)

So what we want to match is of type reflect.runtime.universe.Type. Note that in the last line, String and scala.Int are just string representations of these types not their actual type!

So we need to match them against different values of this type which we can easily get with typeOf[Int] and typeOf[String].

A bit more on pattern matching in Scala

You may want to make the code more concise by using the following code:

typeOf[CaseClass].members.withFilter(!_.isMethod).map(_.typeSignature).foreach {
  case typeOf[Int] => print("i")    // Won't Compile!
  case typeOf[Int] => print("s")    // Won't Compile
}

But this give you the following compile error:

not found: type typeOf

Again this is because here we need to match against a variable name beginning with an uppercase letter. So the following works:

val T = typeOf[Int]
val S = typeOf[String]

typeOf[CaseClass].members.withFilter(!_.isMethod).map(_.typeSignature).foreach {
  case T => print("i")
  case S => print("s")
}

For more details on pattern matching, refer to Programming in Scala. You will find a detailed explanation of pattern matching there.

like image 120
Nader Ghanbari Avatar answered Oct 06 '22 11:10

Nader Ghanbari