Reputation: 2098
This question is a little complex, so I'm building up to it with some preamble so that the driver behind the question, and the code sample that demonstrates it, can be understood.
Immutable Builder pattern
I like traits a lot, but they can give problems with the immutable builder pattern. For example when I create an object that inherits the builder trait, when I call a 'set' method on it, the builder trait is supposed to return me an identical clone of the original, apart from the value I have just set.
To deal with this I often use a type statement
trait Builder {
type RealBuilder <: Builder
...
This doesn't work fantastically well, but it's adequate for most purposes.
Universe Pattern
I am experimenting with an idea taken from the Scala compiler. I have about ten generics, but I don't want the user to see most of them, and the generics are often linked together in complicated ways
trait Universe[X] {
type A
type B
type RealBuilder <: MyBuilder
type RealBuiltThing <: MyBuiltThing
trait MyBuilder {
def build:
}
trait MyBuiltThing {
}
}
So the nice thing here is that everything defined inside the Universe shares the same generics, and the code for those classes, and their usage isn't polluted by tons of generics.
Finally the question
I want to pass a function to the builder inside the universe and get a nice 'toString. So I am wrapping the function (which in this simple example is just X) and then passing the wrapped object to the setIt method
class Wrapper[X](x: X, string: String) {
override def toString() = string
}
object Outer {
def someProperty[X: c.WeakTypeTag](c: Context)(someValue: c.Expr[X]):
c.Expr[Outer#InnerBuilder] = { // <----------- This is the first line referenced below
import c.universe._
val xString = show(someValue.tree)
reify { c.Expr[Outer#InnerBuilder](c.prefix.tree).splice.setIt(new Wrapper[X](someValue.splice, c.literal(xString).splice)) }
}
}
class Outer {
trait InnerBuilder {
type RealInnerBuilder <: InnerBuilder;
type B;
def someProperty[X](someValue: X) = macro Outer.someProperty[X]
def setIt[X](w: Wrapper[X]): RealInnerBuilder = {
println("Setting it to " + w) //shows that the code got here
this.asInstanceOf[RealInnerBuilder] //in practice would return a new instance that held the wrapper
}
}
class InnerBuilder1 extends InnerBuilder {
type RealInnerBuilder = InnerBuilder1
}
}
If I now create an InnerBuilder1 and call 'someProperty', the println statement executes. Hurrah!
BUT and it's a big but... I've lost some type safety. The companion object is returning an object of c.Expr[Outer#InnerBuilder], and what it really wants to do is to return the InnerBuilder class of 'c.prefix'.
I'm sad to say that I don't fully understand the [] notation is Scala. So the following are probably just naive. I've tried returning c.Expr[c.prefix.actualType], which is 'the idea' of what I want, but it obviously isn't correct.
Could anyone advise me on how to bring the type safety back to this Macro?
Upvotes: 2
Views: 396
Reputation: 13048
I think you might be interested in Context.PrefixType
and Expr.value
. Here's a relevant example from our test suite: https://github.com/scala/scala/blob/d5801b9eee7df49894c05dea430a56190cae2112/test/files/run/macro-def-path-dependent-b/Impls_Macros_1.scala#L19.
Also, no type safety is actually lost, because macro expansions in 2.10 have this funny property of admitting the most precise type possible rather than just what is specified in their signatures (e.g. take a look at http://meta.plasm.us/posts/2013/06/19/macro-supported-dsls-for-schema-bindings/). In 2.11 this will change a bit (http://docs.scala-lang.org/overviews/macros/blackbox-whitebox.html), but this capability to refine return types will still remain.
Finally, it might be relieving to know that in 2.11 there is no longer a need in using Expr
's and reify
. With quasiquotes that are shipped with 2.11 (that are also available in 2.10 via the macro paradise compiler plugin) you can throw together trees almost in whatever fashion you want without having to making the types align. With the updated rules for writing macro impls, macro impls can take and return c.Tree
's, so that you don't need to think what to put in the []
's of c.Expr
.
Upvotes: 4