Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to integrate Cake-Pattern and Macros?

I must integrate some macros in a project which is using a cake-pattern. That pattern allowed us to avoid zillions of imports, among other advantages, so we would like to keep it. Now, we are facing a problem with some experimental macros we have been testing outside the trunk. First, let's show a dummy system named Cake:

trait APiece {
  class A
}

trait BPiece { this: APiece => 
  def aMacro(a: A): Unit = () /* macro ??? */
}

trait CPiece { this: APiece with BPiece =>
  def aMacroInvoker = aMacro(new A)
}

class Cake { this: APiece with BPiece with CPiece => }

APiece defines a class, BPiece is supposed to be a macro which uses the APiece defined class, and finally, CPiece invokes the macro. I said that BPiece was supposed to be a macro since I was unable to code an implementation for it. I have tried several ways but I always crash with the following error:

"macro implementation must be in statically accessible object"

Reading the macros code one can guess that it is neccesary to enclose the macro in a static module. Is there any way to deploy a macro which uses the system structures?

like image 564
jeslg Avatar asked Oct 07 '22 09:10

jeslg


1 Answers

Luckily there's an easy solution to your problem.

But first, let me give some retrospective. In the very first prototype, macros were defined like that: def macro aMacro(a: A): Unit = .... One of the major breakthroughs we've had when preparing a SIP is separating macro definitions (public faces of macros) and macro implementations (tree transformers that host macro logic). It took me a while realize how cool this is, but now I'm glowing with joy every time when I write a macro declaration.

So, back to your question. Sure, macro implementations have to be statically accessible (otherwise, compiler won't be able to load and invoke them during the compilation). However macro definitions don't have this restriction, so you can write the definition like that:

trait BPiece { this: APiece => 
  def aMacro(a: A): Unit = macro Macros.aMacro
}

Macro implementation that is referred to from the definition can be put into whatever object you wish, even into a different compilation unit.

The only missing piece of puzzle is how we're going to refer to A from the implementation, because A is defined inside a cake. The simplest way would be to make aMacro generic and rely on type inference:

(update: to make this example work in 2.10.0-M7, you need to replace c.TypeTag with c.AbsTypeTag; to make this example work in 2.10.0-RC1, c.AbsTypeTag needs to be replaced with c.WeakTypeTag)

trait BPiece { this: APiece =>
  def aMacro[A](a: A): Unit = macro Macros.aMacro[A]
}

object Macros {
  def aMacro[A: c.TypeTag](c: Context)(a: c.Expr[A]): c.Expr[Unit] = c.literalUnit
}

This won't let you use reify, though, because for a macro implementation A is just a type parameter without any members. There are also going to be problems if you'll want to return something cake-specific from the macro, but let's deal with them when they arise. Please, submit follow-up questions if you need to.

like image 159
Eugene Burmako Avatar answered Oct 13 '22 10:10

Eugene Burmako