Is it possible to write a scala macro whose returntype depends on argument?

蹲街弑〆低调 提交于 2019-12-04 18:59:31

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]())
  }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!