For a DSL I would like to be able to do something like:
object Creator {
def create[T](s :String) :Foo[T] = macro createImpl[T]
def createImpl[T](c :Context)(s :c.Expr[String]) : c.Expr[Foo[T]] = {
reify(new Foo[Any]())
}
}
My problem is to replace the Any
in reify with something that will return the correctly parametrized version.
(above I use a string argument, but in the final version I plan to use the companion object of class T as a marker to know the argument-type of a Function1[T,Unit])
You need to:
a) write new Foo[T]()
instead of new Foo[Any]()
(easy)
b) pass in the macro a representation of type T
, namely, a value of type AbsTypeTag[T]
, by declaring the parameter T
using a context bound: [T: c.AbsTypeTag]
.
Here's code which I tested in Scala 2.10.0-M7. Edit. In 2.10.0-RC1 AbsTypeTag
has been renamed to WeakTypeTag
. Everything else about macros and type tags remains the same.
Creator.scala:
import language.experimental.macros
import reflect.macros.Context
class Foo[T]
object Creator {
def create[T](s: String): Foo[T] = macro createImpl[T]
def createImpl[T: c.AbsTypeTag](c: Context)(s: c.Expr[String]): c.Expr[Foo[T]] = {
import c.universe._
reify(new Foo[T]())
}
}
MacroClient.scala:
object Main extends App {
println (Creator.create[Int](""))
}
Note that if you omit the type parameter, you will get a weird error:
scala> Creator.create[Int]("")
res2: Foo[Int] = Foo@4888884e
scala> Creator.create("")
<console>:12: error: macro has not been expanded
Creator.create("")
^
You also write:
(above I use a string argument, but in the final version I plan to use
the companion object of class T as a marker to know the argument-type
of a Function1[T,Unit])
but if I get it right, this sounds like a bad idea. Instead of writing Creator.create[T](otherArgs)
, the call syntax would be something like Creator.create(T, otherArgs)
, not a big advantage (if any). But you can't even get the latter syntax: if class A
and object A
are companions, their types are not related: the first has type A
, the second has type A.type
where A
is the companion object and not the type of class A
.
Update: how to get the Creator create Foo
syntax to work and return an instance of Foo
, if you have control over Foo
.
Since you ask about the Any
type argument to reify
, I assume you are asking about the type argument. That makes only sense if you want the static return type of Creator.create
to be T
and not Any
; otherwise, you should clarify your question.
The problem here has little to do with macros. Creator create Foo
passes the object Foo
to Creator.create
, whose declaration needs to express, given Foo.type
, the type Foo
through a type expression. Type expressions in Scala are quite limited - they can't use reflection, for instance. But given a type, they can select its type members.
trait Companion[Class]
//How to declare a companion
class Foo
object Foo extends Companion[Foo]
/*I'm cheating: an implementation of Companion does not need to be a true Companion. You can add documentation to explain how Companion is supposed to be used. */
object Bar extends Companion[Foo]
//But this is also useful - you can't create your own companion objects for pre-existing types, but you can still create the right instances of Companion:
object pInt extends Companion[Int]
object Creator {
//T with Companion[S] is needed to workaround a type inference bug (SI-5298) and allow S to be correctly inferred.
def create[S, T <: Companion[S]](obj: T with Companion[S]): S = ???
}
This is limited since you need to alter the companion object, but I'm pretty sure you can't do better. I know no way, in a type expression (what you can use in place of S
when declaring the return type of create
) of getting from a companion object to its associated class type in general, and I don't think there's one.
Now, changing the above to use macros is straightforward:
import language.experimental.macros
import reflect.macros.Context
class Foo[T]
object Creator {
//T with Companion[S] is needed to workaround a type inference bug (SI-5298) and allow S to be correctly inferred.
def create[S, T <: Companion[S]](obj: T with Companion[S]): Foo[S] = macro createImpl[S, T]
def createImpl[S: c.AbsTypeTag, T <: Companion[S]: c.AbsTypeTag](c: Context)(obj: c.Expr[T with Companion[S]]): c.Expr[Foo[S]] = {
import c.universe._
reify(new Foo[S]())
}
}