简体   繁体   中英

Function type of all methods of a single class - in Scala

Not sure how to properly formulate this (hence, how to look it up), but here goes:

I understand how a method applied to an object can become a function object. For example:

case class User(name: String, age: Int)
val user = User("", 0)
user.name _   // () => String

So, if I had a method def meth(f: () => String) , I could do: meth(user.name _)

Is is possible to define a type that has as instances the methods of class User ? (the function objects obtained from these methods, more precisely)

In order words, what would the type of f in def meth(f: ???) be, in order to be able to do this: meth(user.name _) and meth(user.age _)

Thanks!

I guess the only way to do something like that is to use macro's. I once created a macro that does the following (note that some details changed in the current implementation and thus are empty).

case class Metadata(instance: AnyRef, name: String) {

  def value = ??? // use reflection to invoke `name` on `instance`
}

object Metadata extends ((AnyRef, String) => Metadata) {

  implicit def anyToMetadata(sym: Any): Metadata = 
    macro MetadataMacro.anyToMetadataImpl
}

private[staticReflection] object MetadataMacro {

  def anyToMetadataImpl(c: Context)(sym: c.Expr[Any]): c.Expr[Metadata] = {

    import c.universe._

    val metadata = // match the tree and create the appropriate metadata instance

    c.Expr(metadata)
  }
}

In code you would then use it like this:

case class User(name:String)

def test(metadata:Metadata) {
  println(metadata.name + "->" + metadata.value)
}

val u = User("test")

test(u.name) // name -> test

The code as it was valid almost a year ago can be found here: ee/scala/staticReflection/Metadata.scala . More info about macros as they are now .

If this is what you were looking for, please let me know so I can see if I can convert the original to a working version.


Edit

I managed to get the old one working. To use it simply copy-past the code into a separate project (I use Eclipse) and then link the projects via the Java Build Path . Working version:

package ee.scala.staticReflection

import scala.language.experimental.macros
import scala.reflect.macros.Context
import language.implicitConversions

case class Metadata(instance: AnyRef, name: String) {
  // any comments on how to improve this part are welcome
  val runtimeUniverse = scala.reflect.runtime.universe
  val mirror = runtimeUniverse.runtimeMirror(getClass.getClassLoader)
  val instanceMirror = mirror.reflect(instance)
  val method = instanceMirror.symbol.selfType.member(runtimeUniverse newTermName name).asMethod
  val methodMirror = instanceMirror.reflectMethod(method)

  def value = methodMirror()
}

object Metadata extends ((AnyRef, String) => Metadata) {

  implicit def anyToMetadata(sym: Any): Metadata = macro MetadataMacro.anyToMetadataImpl
}

private[staticReflection] object MetadataMacro {

  def anyToMetadataImpl(c: Context)(sym: c.Expr[Any]): c.Expr[Metadata] = {
    import c.universe._

    def createMetadataInstance(select: Select): Tree =
      treeBuild.mkMethodCall(
        c.mirror.staticModule("ee.scala.staticReflection.Metadata"),
        newTermName("apply"),
        List(select.qualifier, Literal(Constant(select.name.toString))))

    val metadata = sym.tree match {
      //normal select
      case select: Select => createMetadataInstance(select)

      //could be a call using a right associative operator
      case Ident(name) =>
        c.enclosingMethod.collect {
          case ValDef(_, refName, _, select: Select) if refName == name => createMetadataInstance(select)
        }
          .headOption
          .getOrElse(throw new Exception("Could not find ValDef for " + name))

      case _ => throw new Exception("Could not create metadata")
    }

    c.Expr(metadata)
  }
}

Edit 2

To apply all of the above stuff to the question of the original poster. You could use it like this

case class User(name: String, age: Int)
val user = User("", 0)

def meth(metadata: Metadata) = {
  println(metadata.name + "->" + metadata.value)
}

meth(user.name)
meth(user.age)

Note that the solution is different from what was proposed, but within the meth function you can do the same (a bit more even).

I believe what you're looking for is Scala's "structural type" (not really a Scala innovation).

It captures non-nominal typing but its implementation on the JVM necessitates the use of reflection at the point in your code where you want to actually access or invoke the members dictated by the structural type. Hence there's a pretty non-trivial overhead associated with using structural types in Scala. (I recall hearing talk about implementing some kind of cache for the required reflection-derived information, but I don't know if that was ever implemented).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM