pygumby
pygumby

Reputation: 6790

What is the type of a field of a Scala class?

I would like to write a function like the following:

def printFieldOfClass(field: ???) =
  println(field)

Suppose there is a case class definition such as case class A(id: String). It would then be possible to call printFieldOfClass(A.id) which would print the string "A.id". If we, however, try to call printFieldOfClass(A.idd), I would want the code not to compile. Is this even possible? And if so, what is the type of the parameter field?

Help is much appreciated!

EDIT: As there seems to be some confusion about what I am trying to do, let me clarify: I do not want to pass an instance of the case class, I much rather want to pass a reference to some field in a class definition. Also I do not want my function to be hard wired to any such class, it should work with all Scala classes, like so: printFieldOfClass(SomeClassTheFunctionDoesNotKnowAbout.someField) should either print "SomeClassTheFunctionDoesNotKnowAbout.someField" in case SomeClassTheFunctionDoesNotKnowAbout's definition specifies a field someField or, if the class has no such field, the call should not compile.

Upvotes: 0

Views: 100

Answers (1)

Alexey Romanov
Alexey Romanov

Reputation: 170733

This isn't possible if printFieldOfClass is a normal method, as the comments explain. But you can make it a macro instead. It's basically a function which runs at compile-time, receives the syntax tree of its argument and generates a new tree to replace it. There are quite a few introductions to Scala macros, and writing another one in the answer doesn't make sense. But I don't advise trying it until you are very comfortable with Scala in general.

An example which does something close to what you want:

import scala.annotation.tailrec
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

object Macros {
  def nameOfImpl(c: blackbox.Context)(x: c.Tree): c.Tree = {
    import c.universe._

    @tailrec def extract(x: c.Tree): String = x match {
      case Ident(TermName(s)) => s
      case Select(_, TermName(s)) => s
      case Function(_, body) => extract(body)
      case Block(_, expr) => extract(expr)
      case Apply(func, _) => extract(func)
    }

    val name = extract(x)
    q"$name"
  }
  def nameOfMemberImpl(c: blackbox.Context)(f: c.Tree): c.Tree = nameOfImpl(c)(f)

  def nameOf(x: Any): String = macro nameOfImpl
  def nameOf[T](f: T => Any): String = macro nameOfMemberImpl
}

//
// Sample usage:
//

val someVariable = ""
Macros.nameOf(someVariable) // "someVariable"

def someFunction(x: Int): String = ???
Macros.nameOf(someFunction _) // "someFunction"

case class SomeClass(someParam: String)
val myClass = SomeClass("")
Macros.nameOf(myClass.someParam) // "someParam"

// without having an instance of the class:
Macros.nameOf[SomeClass](_.someParam) // "someParam"

Upvotes: 2

Related Questions