Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement abstract methods with a def macro

It seems to be impossible to implement abstract methods via def macros:

import scala.reflect.macros.Context
import language.experimental.macros

trait A {
  def foo(): Unit
}

object AImpl {
  def fooImpl(c: Context)(): c.Expr[Unit] = {
    import c.universe._
    c.Expr[Unit](reify().tree)
  }
}
trait AImpl extends A {
  def foo(): Unit = macro AImpl.fooImpl
}

This fails with the following error:

[error] .../A.scala:17: overriding method foo in trait A of type ()Unit;
[error]  macro method foo cannot override an abstract method
[error]   def foo(): Unit = macro AImpl.fooImpl
[error]       ^

If I remove the extends A it compiles. But obviously I want AImpl to satisfy trait A. How to fix this?


Another try:

trait AImpl extends A {
  def foo(): Unit = bar()
  def bar(): Unit = macro AImpl.fooImpl
}

Gives new error:

[error] macro implementation not found: bar (the most common reason for that is that
  you cannot use macro implementations in the same compilation run that defines them)
[error] one error found
like image 648
0__ Avatar asked Jun 17 '13 14:06

0__


2 Answers

Are you sure you did test with the macro compiled first and AImpl later?

Using a forwarder method like your second try seems to work (with 2.10.2):

// first compilation run

import scala.reflect.macros.Context
import language.experimental.macros

trait A {
  def foo(): Unit
}

object AImplMacros {
  def fooImpl(c: Context)(): c.Expr[Unit] = {
    import c.universe._
    c.Expr[Unit](reify().tree)
  }
}

// second compilation run

trait AImpl extends A {
  def foo(): Unit = bar()
  def bar(): Unit = macro AImplMacros.fooImpl
}

// compiles and runs:

scala> val a = new AnyRef with AImpl
a: AImpl = $anon$1@59225446

scala> a.foo

scala> a.bar
like image 108
gourlaysama Avatar answered Nov 15 '22 07:11

gourlaysama


I'm not sure if this is correct, so please add an additional authoritative answer, please.

I'm only starting to understand how def macros work. The wrong assumption in the question is that def bar(): Unit = macro ... actually creates a runtime bar method. Instead it creates... well, a macro, so any call to that macro just splices in the expression.

So I see two thing. Either the return type becomes c.Expr[DefDef], but I'm not sure that's even possible, and probably it's a lot more work. The second option is to generate the whole trait instead, e.g. as an anonymous class:

import scala.reflect.macros.Context
import language.experimental.macros

trait A {
  def foo(): Unit
}

object AImpl {
  def body: A = macro bodyImpl
  def bodyImpl(c: Context): c.Expr[A] = {
    import c.universe._
    val r = reify { new A { def foo() { println("schoko" )}}}
    c.Expr[A](r.tree)
  }
}

Then instead of mixin, you have composition:

object AHolder extends App {
  val bar: A = AImpl.body

  bar.foo()
}

The big suck is, I need to set up a sub project with sbt because otherwise these files don't compile at the same time :-/

like image 35
0__ Avatar answered Nov 15 '22 07:11

0__