2

I am trying to call a specialized collections library like FastUtil or Trove from generic Scala code. I would like to implement something like

def openHashMap[@specialized K, @specialized V]: ${K}2${V}OpenHashMap = 
   new ${K}2${V}OpenHashMap()

Where the ${X} is clearly not valid Scala, but just my meta notation for text substitution, so that openHashMap[Long, Double] would return a Long2DoubleOpenHashMap the type would be known at compile time. Is this possible with Scala macros. If so, which flavour? I know there are def macros, implicit macros, fundep materialization, macro annotations, type macros (now discontinued) ... and I think these are different in plain Scala-2.10, 2.10 macro paradise and Scala-2.11. Which, if any, of these are appropriate for this?

Or is there something else that can do this like byte code manipulation, language virtualization, ...? However, I do not believe the alternative I just mentioned can.

Daniel Mahler
  • 7,653
  • 5
  • 51
  • 90
  • This should be fairly simple with plain `def` macros. Unfortunately I don't have time now to elaborate. – ghik Sep 29 '13 at 09:39
  • Availability of different macro-related features in 2.10, paradise and 2.11 is outlined in a table at http://docs.scala-lang.org/overviews/macros/roadmap.html – Eugene Burmako Sep 29 '13 at 13:47

1 Answers1

2

Stealing the idea to use quasiquotes from this answer, we first add the macro-paradise plugin:

// build.sbt
scalaVersion := "2.10.2"

resolvers += Resolver.sonatypeRepo("snapshots")

addCompilerPlugin("org.scala-lang.plugins" % "macro-paradise" % "2.0.0-SNAPSHOT" 
  cross CrossVersion.full)

Then the macro looks like this:

// src/main/scala/Foo.scala
import reflect.macros.Context
import language.experimental.macros

trait Foo[A]
class IntFoo() extends Foo[Int]
class AnyFoo() extends Foo[Any]

object Foo {
  def apply[A]: Foo[A] = macro applyImpl[A]

  def applyImpl[A](c: Context)(t: c.WeakTypeTag[A]): c.Expr[Foo[A]] = {
    import c.universe._
    val aTpe    = t.tpe
    val prefix  = if (aTpe =:= typeOf[Int]) "Int" else "Any"
    val clazz   = newTypeName(s"${prefix}Foo")
    c.Expr(q"new $clazz()")
  }
}

And the test case:

// src/test/scala/Test.scala
object Test extends App {
  val fooInt = Foo[Int]
  val fooAny = Foo[Any]

  println(fooInt)
  println(fooAny)
}

Without the macro-paradise-plugin, you would need to construct the tree by hand, like New(clazz, ???), I couldn't get that to work so gave up, but it certainly would be possible, too.

Community
  • 1
  • 1
0__
  • 66,707
  • 21
  • 171
  • 266
  • Note that the type of `fooInt` is still `Foo[Int]` and not `IntFoo`. I believe for a synthetic macro return type you would need type-macros (and they have not been accepted for standard Scala). – 0__ Sep 29 '13 at 12:11
  • Actually the type of fooInt even in 2.10.x is IntFoo. – Eugene Burmako Sep 29 '13 at 13:45
  • Also usage of macro paradise here is only necessary for quasiquotes. If one replaces quasiquotes with equivalent manual tree construction, everything will work in vanilla 2.10. – Eugene Burmako Sep 29 '13 at 13:48
  • @EugeneBurmako - ah, I'm surprised. So the return type annotation of `apply` is overriden? Yes, read my last paragraph. I was too lazy to figure out how to invoke the constructor by hand. – 0__ Sep 29 '13 at 13:49
  • 1
    In 2.10 macro expansions can refine return types of macro defs that were used to produce them. In 2.11, it will be a bit different though as described in http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html. – Eugene Burmako Sep 29 '13 at 13:52
  • Will this work in generic code? I mean if I define `def f[X] = ... Foo[X] ...` will the macro expansion only happen when `X` is instantiated or will it use just use `X` directly as the value of `A`? – Daniel Mahler Sep 29 '13 at 21:19
  • 1
    @DanielMahler in that case you will need to use reflection instead, I guess, because there is no resolved type `X` available at compile time. Luckily, 2.10 reflection and macros share some things (e.g. the type test). – 0__ Sep 30 '13 at 12:17