Quasiquotes and case class apply method

268 views
Skip to first unread message

Marc Saegesser

unread,
Dec 17, 2015, 9:04:26 AM12/17/15
to scala-user
Using macros and quasiquotes I'm trying to generate some code along the lines of 

case class Fubar(i: Int)
Option(1).map(Fubar.apply)

An example of the macro code and a sample sbt session is below. I realize these are really dumb macros, but I'm trying to get it down the bare minimum to demonstrate the issue.

If I generate code using something like 

val tpe = c.weakTypeOf[Test.Fubar]
q
"""Option(1).map($tpe.apply)"""

I get the error "error: value apply is not a member of Test.Fubar". If I spell out the class name then it works as expected.

I'm sure I'm missing something silly here, but damned if I can see it.

test.scala:

import scala.reflect.runtime.{universe => ru}
import ru._


object Test {
  type
Context = scala.reflect.macros.blackbox.Context
 
case class Fubar(i: Int)


 
def dumb: Option[Test.Fubar] = macro dumbImpl


 
def dumbImpl(c: Context): c.Expr[Option[Test.Fubar]] = {
   
import c.universe._
    val expr
= q"""Option(1).map(Test.Fubar.apply)"""
    c
.Expr[Option[Test.Fubar]]{expr}
 
}


 
def aaaaarrrrrgggggh: Option[Test.Fubar] = macro arghImpl


 
def arghImpl(c: Context): c.Expr[Option[Test.Fubar]] = {
   
import c.universe._
    val tpe
= c.weakTypeOf[Fubar]
    val expr
= q"""Option(1).map($tpe.apply)"""
    c
.Expr[Option[Test.Fubar]]{expr}
 
}
}



Example session:
scala> :load test.scala
...
scala> Test.dumb
performing macro expansion $line93.$read.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.Test.dumb at source-<console>,line-56,offset=1229
Option(1).map(Test.Fubar.apply)
Apply(Select(Apply(Ident(TermName("Option")), List(Literal(Constant(1)))), TermName("map")), List(Select(Select(Ident(TermName("Test")), TermName("Fubar")), TermName("apply"))))
res16: Option[Test.Fubar] = Some(Fubar(1))

scala> Test.aaaaarrrrrgggggh
performing macro expansion $line93.$read.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.Test.aaaaarrrrrgggggh at source-<console>,line-56,offset=1229
Option(1).map($line93.$read.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.$iw.Test.Fubar.apply)
Apply(Select(Apply(Ident(TermName("Option")), List(Literal(Constant(1)))), TermName("map")), List(Select(TypeTree(), TermName("apply"))))
<console>:56: error: value apply is not a member of Test.Fubar
       Test.aaaaarrrrrgggggh
            ^

scala> 

Marc Saegesser

unread,
Dec 17, 2015, 10:48:56 AM12/17/15
to scala-user
Answering my own question. It looks like the following might work. The apply method is on the companion object. I went down this path earlier and it failed but I realized that I needed to get the type symbol for the companion rather than using the type directly.

val companion = t.companion.typeSymbol
q
"""Option(1).map($companion.apply)"""

Denys Shabalin

unread,
Dec 18, 2015, 6:50:26 AM12/18/15
to Marc Saegesser, scala-user
Using symbols is usually the right solution in the situations like these.

Cheers,
Denys

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Marc Saegesser

unread,
Dec 18, 2015, 8:59:51 AM12/18/15
to scala-user, marcsa...@gmail.com
Not sure what I was smoking when I wrote my reply above because that doesn't work. Still trying to figure out how to access the apply method on the companion class.

Oliver Ruebenacker

unread,
Dec 18, 2015, 11:27:41 AM12/18/15
to Marc Saegesser, scala-user

     Hello,

  How about this?

case class Fubar(i: Int)
Option(1).map(Fubar.apply(_))

     Best, Oliver
--
Oliver Ruebenacker
Senior Software Engineer, Diabetes Portal, Broad Institute

Marc Saegesser

unread,
Dec 18, 2015, 11:46:11 AM12/18/15
to scala-user, marcsa...@gmail.com
Adding the explicit parameter list doesn't help. In

val tpe = c.weakTypeOf[Test.Fubar]

q
"""Option(1).map($tpe.apply(_))"""

$tpe is still the 'class Fubar', not the 'object Fubar'.

Oliver Ruebenacker

unread,
Dec 18, 2015, 11:48:34 AM12/18/15
to Marc Saegesser, scala-user

     Hello,

  Maybe this?

val tpe = c.weakTypeOf[Test.Fubar.type]
q
"""Option(1).map($tpe.apply(_))"""

     Best, Oliver

Marc Saegesser

unread,
Dec 18, 2015, 12:00:47 PM12/18/15
to scala-user, marcsa...@gmail.com
The Fubar type comes into the macro as a type parameter, e.g.

def myMacro[T: c.weakTypeTag](c: Context): c.Expr[Option[T]] = {
   val tpe
= c.weakTypeOf[T]
   
...
}


so I can't do

  c.weakTypeOf[T.type]

However, the results of c.weakTypeOf[Test.Fubar.type] is exactly what you would get from c.weakTypeOf[Fubar].companion. I haven't been able to get from the companion type to a symbol that I can put into the quasiquote in such a way that it gets interpreted as the companion object.

Everything I've tried either doesn't compile or get's interpreted as the class rather than the object.

Jasper-M

unread,
Dec 21, 2015, 7:59:42 AM12/21/15
to scala-user, marcsa...@gmail.com
This is a bit hacky, but maybe it works...

q"""Option(1).map(${TermName(tpe.toString)}.apply)"""

The `Fubar` in `Fubar.apply` is an object, not a type, but I wouldn't know how to get at that object when you have the corresponding type.

Op vrijdag 18 december 2015 18:00:47 UTC+1 schreef Marc Saegesser:

Marc Saegesser

unread,
Dec 21, 2015, 11:00:22 PM12/21/15
to scala-user, marcsa...@gmail.com
Curiously, this works when tpe is in the top level package (e.g. Fubar) but fails when it is in a real package (e.g. com.fu.Fubar). In fact, I think this the scenario I ran into a before when I thought I had something that worked but then it failed when I tried to us it in my real application.

Gavin Baumanis

unread,
Dec 23, 2015, 8:16:16 AM12/23/15
to scala-user, marcsa...@gmail.com
Hi Marc,

I also find things don't work when I prefix them with "com.mycompany.myapplication" - but when they're in the "root" package - they work fine.

I end up spending a lot of redundant time and effort, trying to replicate the issue in a "root" based application - where it works.
So I replicate it in my "proper" application - only for it not to work (again) ....
And... So I end with trying to do"imports" that I am certain I shouldn't need or other pieces of code that just seem completely "hacky" to get it working.

It drives me crazy!!!
And because I am relatively new to Scala / Play / Akka - so I can't authoritatively answer;
"Is this because of me - or this weird "package" issue that I "very often" get tripped up by.

-Gavin

Lance Gatlin

unread,
Dec 24, 2015, 6:38:53 PM12/24/15
to scala-user
Hey Marc!

Apologies for the slow reply. Was hoping one of the *Scala Macro Gods* would step in and answer your question. Since I didn't see any definitive answers yet and I'm on Christmas break, I went ahead and poked around until I got something working. I most def not a Scala Macro God. Perhaps my hacky answer will anger one of them enough to give you a proper answer haha.

So when working with quasiquotes, don't forget you can use the showRaw code method to explore whats going on:

Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_79).

Type in expressions to have them evaluated.

Type :help for more information.


scala> import scala.reflect.runtime.universe._

import scala.reflect.runtime.universe._


scala> object Test { case class A(i: Int) }

defined object Test


scala> showRaw(q"Test.A.apply(1)")

res0: String = Apply(Select(Select(Ident(TermName("Test")), TermName("A")), TermName("apply")), List(Literal(Constant(1))))


scala> val tpe = typeOf[Test.A]

tpe: reflect.runtime.universe.Type = Test.A


scala> showRaw(q"$tpe.apply(1)")

res1: String = Apply(Select(TypeTree(), TermName("apply")), List(Literal(Constant(1))))



As you can see the first AST correctly builds the Select, but the second fails to work. The key idea here is that the "Test.A.apply" is forming a Select and quasiquotes isn't smart enough to figure out we want to interpolate that type into that Select. Also I'm not smart enough to know what quasiquotes is doing instead here.


scala> showRaw(q"${tpe.companion}.apply(1)")
res2: String = Apply(Select(TypeTree(), TermName("apply")), List(Literal(Constant(1))))


scala> showRaw(q"${tpe.companion.typeSymbol}.apply(1)")

res3: String = Apply(Select(Ident(Test.A), TermName("apply")), List(Literal(Constant(1))))



These don't work either. The last is close, but now we can see that what we need is a TermName and not a TypeSymbol. The following is one way to make it work:

scala> showRaw(q"import Test._; ${tpe.typeSymbol.name.toTermName}.apply(1)")

res4: String = Block(List(Import(Ident(TermName("Test")), List(ImportSelector(termNames.WILDCARD, -1, null, -1)))), Apply(Select(Ident(TermName("A")), TermName("apply")), List(Literal(Constant(1)))))


This requires knowing ahead of time what needs to be imported though. After poking around for 5 mins (extent of my attention span) I couldn't find anything to build a Select with the full owner path from a Symbol. So I wrote you a quick and dirty function:


def selectFullTermName(c:Context)(sym: c.Symbol) : c.Tree  = {
  import c.universe._
  sym.fullName.split('.').toList match {
    case Nil => throw new RuntimeException("unreachable")
    case head :: tail =>
      tail.foldLeft(Ident(TermName(head)).asInstanceOf[c.Tree]) { case (qualifier,next) =>
        Select(qualifier,TermName(next))
      }
  }
}


Now to get the Select built right, we have to start with what we are ultimately trying to select which is the apply method:



val tpe = c.weakTypeOf[Fubar]
val applyMethod = tpe.companion.member(TermName("apply"))
val expr = q"""Option(1).map(${selectFullTermName(c)(applyMethod)})"""


I've attached updated test.scala as well. I'm hoping someone else has prettier answer =]


Enjoy,


Lance

build.sbt
test.scala

Marc Saegesser

unread,
Dec 24, 2015, 8:26:27 PM12/24/15
to scala-user
Lance,

Thanks. That's pretty nice.

Where 'nice' means twisted, convoluted but functional!

--Marc
Reply all
Reply to author
Forward
0 new messages