Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: How to invoke method with type parameter and manifest without knowing the type at compile time?

I have a function with the following signature:

myFunc[T <: AnyRef](arg: T)(implicit m: Manifest[T]) = ???

How can I invoke this function if I do not know the exact type of the argument at the compile time?

For example:

val obj: AnyRef = new Foo()    // At compile time obj is defined as AnyRef,
val objClass = obj.getClass    // At runtime I can figure out that it is actually Foo
// Now I would need to call `myFunc[Foo](obj.asInstanceOf[Foo])`,
// but how would I do it without putting [Foo] in the square braces?

I would want to write something logically similar to:

myFunc[objClass](obj.asInstanceOf[objClass])

Thank you!

UPDATE:

The question is invalid - As @DaoWen, @Jelmo and @itsbruce correctly pointed, the thing I was trying to do was a complete nonsense! I just overthought the problem severely. THANK YOU guys! It's too bad I cannot accept all the answers as correct :)

So, the problem was caused by the following situation:

I am using Salat library to serialize the objects to/from BSON/JSON representation. Salat has an Grater[T] class which is used for both serialization and deserialization. The method call for deserialization from BSON looks this way:

val foo = grater[Foo].asObject(bson)

Here, the role of type parameter is clear. What I was trying to do then is to use the same Grater to serialize any entity from my domain model. So I wrote:

val json = grater[???].toCompactJSON(obj)

I immediately rushed for reflection and just didn't see an obvious solution lying on the surface. Which is:

grater[Entity].toCompactJSON(obj)  // where Entity...

@Salat trait Entity                // is a root of the domain model hierarchy

Sometimes things are much easier than we think they are! :)

like image 409
Alex Vayda Avatar asked Dec 25 '22 20:12

Alex Vayda


2 Answers

It appears that while I was writing this answer the author of the question realized that he does not need to resolve Manifests at runtime. However, in my opinion it is perfectly legal problem which I resolved successfully when I was writing Yaml [de]serialization library, so I'm leaving the answer here.


It is possible to do what you want using ClassTags or even TypeTags. I don't know about Manifests because that API is deprecated and I haven't worked with it, but I believe that with manifests it will be easier since they weren't as sophisticated as new Scala reflection. FYI, Manifest's successor is TypeTag.

Suppose you have the following functions:

def useClasstag[T: ClassTag](obj: T) = ...

def useTypetag[T: TypeTag](obj: T) = ...

and you need to call then with obj: AnyRef as an argument while providing either ClassTag or TypeTag for obj.getClass class as the implicit parameter.

ClassTag is the easiest one. You can create ClassTag directly from Class[_] instance:

useClasstag(obj)(ClassTag(obj.getClass))

That's all.

TypeTags are harder. You need to use Scala reflection to obtain one from the object, and then you have to use some internals of Scala reflection.

import scala.reflect.runtime.universe._
import scala.reflect.api
import api.{Universe, TypeCreator}

// Obtain runtime mirror for the class' classloader
val rm = runtimeMirror(obj.getClass.getClassLoader)

// Obtain instance mirror for obj
val im = rm.reflect(obj)

// Get obj's symbol object
val sym = im.symbol

// Get symbol's type signature - that's what you really want!
val tpe = sym.typeSignature

// Now the black magic begins: we create TypeTag manually
// First, make so-called type creator for the type we have just obtained
val tc = new TypeCreator {
  def apply[U <: Universe with Singleton](m: api.Mirror[U]) =
    if (m eq rm) tpe.asInstanceOf[U # Type]
    else throw new IllegalArgumentException(s"Type tag defined in $rm cannot be migrated to other mirrors.")
}
// Next, create a TypeTag using runtime mirror and type creator
val tt = TypeTag[AnyRef](rm, tc)

// Call our method
useTypetag(obj)(tt)

As you can see, this machinery is rather complex. It means that you should use it only if you really need it, and, as others have said, the cases when you really need it are very rare.

like image 141
Vladimir Matveev Avatar answered May 09 '23 16:05

Vladimir Matveev


This isn't going to work. Think about it this way: You're asking the compiler to create a class Manifest (at compile time!) for a class that isn't known until run time.

However, I have the feeling you're approaching the problem the wrong way. Is AnyRef really the most you know about the type of Foo at compile time? If that's the case, how can you do anything useful with it? (You won't be able to call any methods on it except the few that are defined for AnyRef.)

like image 25
DaoWen Avatar answered May 09 '23 16:05

DaoWen