I need some help writing a macro that produces a pattern match.
This is as far as I got:
import scala.reflect.macros.Context
import language.experimental.macros
trait JsValue
object SealedTraitFormat {
def writesImpl[A: c.WeakTypeTag](c: Context)(value: c.Expr[A]): c.Expr[JsValue] = {
val aTpeW = c.weakTypeOf[A]
val aClazz = aTpeW.typeSymbol.asClass
require(aClazz.isSealed, s"Type $aTpeW is not sealed")
val subs = aClazz.knownDirectSubclasses
require(subs.nonEmpty , s"Type $aTpeW does not have known direct subclasses")
import c.universe._
val cases = subs.toList.map { sub =>
val pat = Bind(newTermName("x"), Typed(Ident("_"),
c.reifyRuntimeClass(sub.asClass.toType)))
val body = Ident("???") // TODO
CaseDef(pat, body)
}
val m = Match(value.tree, cases)
c.Expr[JsValue](m)
}
}
trait SealedTraitFormat[A] {
def writes(value: A): JsValue = macro SealedTraitFormat.writesImpl[A]
}
Here is an example:
sealed trait Foo
case class Bar() extends Foo
case class Baz() extends Foo
object FooFmt extends SealedTraitFormat[Foo] {
val test = writes(Bar())
}
The current error is:
[warn] .../FooTest.scala:8: fruitless type test: a value of type play.api.libs.json.Bar cannot also be a Class[play.api.libs.json.Bar]
[warn] val test = writes(Bar())
[warn] ^
[error] .../FooTest.scala:8: pattern type is incompatible with expected type;
[error] found : Class[play.api.libs.json.Bar](classOf[play.api.libs.json.Bar])
[error] required: play.api.libs.json.Bar
[error] val test = writes(Bar())
[error] ^
(note that play.api.libs.json is my package, so that's correct). I'm not sure what to make of this error...
The expanded macro should look like this
def writes(value: Foo): JsValue = value match {
case x: Bar => ???
case x: Baz => ???
}
It appears to me, that it probably looks like case x: Class[Bar] => ??? now. So my guess is I need to use reifyType instead of reifyRuntimeClass. Basically, how do I get the tree from a Type?
The following seems to work, or at least compile:
val cases = subs.toList.map { sub =>
val pat = Bind(newTermName("x"), Typed(Ident("_"), Ident(sub.asClass)))
val body = reify(???).tree // TODO
CaseDef(pat, body)
}
Related
I want to be able to dynamically choose which extractors to use in my case class pattern matching.
I want something like:
def handleProcessingResult(extract: SomeType) : PartialFunction[Event, State] = {
case Event(someEvent: SomeEvent, extract(handlers)) =>
...
case Event(otherEvent: OtherEvent, extract(handlers)) =>
...
}
The idea is that I can have the above partial function, and can then use it anywhere where I know how to write an unapply to match and extract handlers from some pattern matched type.
If you are wondering why I want these partial functions, it is so that I can compose partial functions of common behaviour together to form the handlers for my states in an Akka FSM. This is not required to understand the question, but for example:
when(ProcessingState) {
handleProcessingResult(extractHandlersFromProcessing) orElse {
case Event(Created(path), Processing(handlers)) =>
...
}
}
when(SuspendedState) {
handleProcessingResult(extractHandlersFromSuspended) orElse {
case Event(Created(path), Suspended(waiting, Processing(handlers))) =>
...
}
It seems like this should be possible but I can't figure out how!
I have tried the following two simplifications:
object TestingUnapply {
sealed trait Thing
case class ThingA(a: String) extends Thing
case class ThingB(b: String, thingA: ThingA) extends Thing
val x = ThingA("hello")
val y = ThingB("goodbye", ThingA("maybe"))
process(x, new { def unapply(thing: ThingA) = ThingA.unapply(thing)})
process(y, new { def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)})
def process(thing: Thing, extract: { def unapply[T <: Thing](thing: T): Option[String]}) = thing match {
case extract(a) => s"The value of a is: $a"
}
}
The idea being that I should be able to pass any sub-type of Thing and a suitable extractor to process. However, it doesn't compile due to:
[error] /tmp/proj1/TestUnapply.scala:10: type mismatch;
[error] found : AnyRef{def unapply(thing: TestingUnapply.ThingA): Option[String]}
[error] required: AnyRef{def unapply[T <: TestingUnapply.Thing](thing: T): Option[String]}
[error] process(x, new { def unapply(thing: ThingA) = ThingA.unapply(thing)})
[error] ^
[error] /tmp/proj1/TestUnapply.scala:11: type mismatch;
[error] found : AnyRef{def unapply(thing: TestingUnapply.ThingB): Option[String]}
[error] required: AnyRef{def unapply[T <: TestingUnapply.Thing](thing: T): Option[String]}
[error] process(y, new { def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)})
[error] ^
Subsequently, moving the declaration of type parameter T onto process, gives us:
import scala.reflect.ClassTag
object TestingUnapply {
sealed trait Thing
case class ThingA(a: String) extends Thing
case class ThingB(b: String, thingA: ThingA) extends Thing
val x = ThingA("hello")
val y = ThingB("goodbye", ThingA("maybe"))
process(x, new { def unapply(thing: ThingA) = ThingA.unapply(thing)})
process(y, new { def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)})
def process[T <: Thing: ClassTag](thing: Thing, extract: { def unapply(thing: T): Option[String]}) = thing match {
case extract(a) => s"The value of a is: $a"
}
}
Now gives us a different compilation error of:
[error] /tmp/TestUnapply.scala:18: Parameter type in structural refinement may not refer to an abstract type defined outside that refinement
[error] def process[T <: Thing: ClassTag](thing: Thing, extract: { def unapply(thing: T): Option[String]}) = thing match {
I am most likely doing something daft. Can someone help me please?
Try to make an workaround based on your first simplification, hope it helps.
object DynamicPattern extends App {
sealed trait Thing
case class ThingA(a: String) extends Thing
case class ThingB(b: String, thingA: ThingA) extends Thing
// change structural type to an abstract class
abstract class UniversalExtractor[T <: Thing] {
def unapply(thing: T): Option[String]
}
// extract is an instance of UniversalExtractor with unapply method
// naturally it's an extractor
def process[T <: Thing](thing: T, extract: UniversalExtractor[T]) =
thing match {
case extract(a) => s"The value of a is: $a"
}
val x = ThingA("hello")
val y = ThingB("goodbye", ThingA("maybe"))
val result1 = process(
x,
new UniversalExtractor[ThingA] {
def unapply(thing: ThingA) = ThingA.unapply(thing)
}
)
val result2 = process(y,
new UniversalExtractor[ThingB] {
def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)
}
)
// result1 The value of a is: hello
println(" result1 " + result1)
// result2 The value of a is: maybe
println(" result2 " + result2)
}
Update
A probably "nasty" method without using an abstract class or trait hinted by the type conformance problem I explained later.
// when invoking process method, we actually know which subtype of
// Thing is pattern-matched, plus the type conformance problem,
// so here comes the ```unapply[T <: Thing](thing: T)```
// and ```asInstanceOf(which is usually not that appealing)```.
val thingAExtractor = new {
def unapply[T <: Thing](thing: T): Option[String] =
ThingA.unapply(thing.asInstanceOf[ThingA])
}
val thingBExtractor = new {
def unapply[T <: Thing](thing: T): Option[String] =
ThingB.unapply(thing.asInstanceOf[ThingB]).map(_._2.a)
}
// hello
println(process(x, thingAExtractor))
// maybe
println(process(y, thingBExtractor))
The reason it doesn't work in the two simplifications(actually the nasty method just pops up in my mind when I try to figure out the reason, so just write it here in case it helps).
For the first simplication: it's about the type conformance problem.
type ExtractType = { def unapply[T <: Thing](thing: T): Option[String] }
val anonExtractor = new { def unapply(thing: ThingA) = ThingA.unapply(thing) }
import scala.reflect.runtime.{ universe => ru }
import scala.reflect.runtime.universe.{ TypeTag, typeTag }
def getTypeTag[T: TypeTag](o: T) = typeTag[T].tpe
def getTypeTag[T: TypeTag] = ru.typeOf[T]
// false | so in turn type check fails at compilation time
println(getTypeTag(anonExtractor) <:< getTypeTag[ExtractType])
ScalaDoc Reflection's Runtime Classes in Java vs. Runtime Types in Scala part demonstrates the type conformance in similar case. In short, Scala compiler creates synthetic classes that are used at runtime in place of user-defined classes to be translated to equivalent Java Bytecode in cases mentioned in that part.
For the second simplication: this post parameter-type-in-structural-refinement-may-not-refer-to-an-abstract-type-define-outside has given some detailed explanation.
I'm using an implicit macro to generate a typeclass.
trait ColumnType[+A]
object ColumnType {
implicit def materializeColumnType[A <: Product]: ColumnType[A] = macro MappedColumnTypeMacro.materializeColumnType[A]
}
case class MappedColumnTypeMacro(c: Context) {
import c.universe._
def materializeColumnType[A: c.WeakTypeTag]: c.Tree = {
val typeOfA = c.weakTypeOf[A]
val companion = typeOfA.typeSymbol.companion
val applyMethod = findMethod(companion.typeSignature, "apply", typeOfA)
val unapplyMethod = findMethod(companion.typeSignature, "unapply", typeOfA)
val typeOfB = applyMethod.paramLists.head.head.asTerm.typeSignature
q"MappedColumnType[$typeOfA, $typeOfB]($companion.$applyMethod, $companion.$unapplyMethod(_).get)"
}
def findMethod(companionType: Type, name: String, typeOfA: Type) = {
companionType.member(TermName(name)) match {
case method: MethodSymbol if method.paramLists.flatten.length == 1 => method
case _ => c.abort(c.enclosingPosition, s"No matching $name method found on $typeOfA")
}
}
}
def column[A](name: String)(implicit columnType: ColumnType[A]) =
TableColumn[A](tableAlias, name)(columnType)
case class WrappedInt(int: Int)
// This fails
val myColumn = column[WrappedInt]("mycolumn")
The reason it fails is because typeOfA is not being resolved to be WrappedInt. When using whitebox context this is resolved as A and when using blackbox context this is resolved as Nothing
Am I doing something wrong or is there a workaround?
The problem is in the variance annotation - scalac considers ColumnType[Nothing] to be the most specific implicit candidate here, so it's going to infer A in materializeColumnType to Nothing regardless of what A is provided in the invocation of column.
In our macro workshop at flatMap 2014, we explain how to work around the issue: https://github.com/scalamacros/macrology201/commit/78779cc7f565dde003fe0da9e5357821b009917b.
Is there a way to return a List of TypeSymbols for each class under a package using macros?
What I am trying to achieve is to write a macro that gives out something equivalent to this list:
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> case class MyClass1()
defined class MyClass1
scala> case class MyClass2()
defined class MyClass2
scala> val typeSymbols = List(typeOf[MyClass1].typeSymbol, typeOf[MyClass2].typeSymbol)
typeSymbols: List[reflect.runtime.universe.Symbol] = List(class MyClass1, class MyClass2)
Here's my setup:
I have a package named foo, under which these are defined:
trait FooTrait
case class Bar() extends FooTrait
case class Bar() extends FooTrait
Here's my macro that gets all type symbols for the classes under foo that extend FooTrait:
def allTypeSymbols_impl[T: c.WeakTypeTag](c: Context)(packageName: c.Expr[String]) = {
import c.universe._
// Get package name from the expression tree
val Literal(Constant(name: String)) = packageName.tree
// Get all classes under given package name
val pkg = c.mirror.staticPackage(name)
// Obtain type symbols for the classes - implementation omitted
val types = getTypeSymbols(c.universe)(List(pkg))
// Apply method for List. For easy readability in later applications
val listApply = Select(reify(List).tree, newTermName("apply"))
val result = types.map {
t =>
val typeName = c.Expr[TypeSymbol](Ident(t))
println(s"Typename: $typeName, $t, ${t.toType}")
reify(typeName.splice).tree
}
println(s"RESULT: ${showRaw(result)}")
c.Expr[List[reflect.runtime.universe.TypeSymbol]](Apply(listApply, result.toList))
}
The first println prints:
Typename: Expr[c.universe.TypeSymbol](Bar), class Bar, foo.Bar
Typename: Expr[c.universe.TypeSymbol](Baz), class Baz, foo.Baz
The second one prints:
RESULT: List(Ident(foo.Bar), Ident(foo.Baz))
But I get this error message:
[error] no type parameters for method any2ArrowAssoc: (x: A)ArrowAssoc[A] exist so that it can be applied to arguments (<notype>)
[error] --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error] found : <notype>
[error] required: ?A
[error] Note that <none> extends Any, not AnyRef.
[error] Such types can participate in value classes, but instances
[error] cannot appear in singleton types or in reference comparisons.
What should I do to make this work? I suspect that I have to write something else instead of Ident, but I couldn't figure out what.
Using Scala 2.10.2.
Thanks in advance!
You have to use reifyType to create reflection artifacts in the runtime universe:
import scala.language.experimental.macros
import scala.reflect.macros.Context
object PackageMacros {
def allTypeSymbols[T](packageName: String) = macro allTypeSymbols_impl[T]
def allTypeSymbols_impl[T: c.WeakTypeTag](c: Context)(
packageName: c.Expr[String]
) = {
import c.universe._
val pkg = packageName.tree match {
case Literal(Constant(name: String)) => c.mirror.staticPackage(name)
}
val types = pkg.typeSignature.members.collect {
case sym: ClassSymbol =>
c.reifyType(treeBuild.mkRuntimeUniverseRef, EmptyTree, sym.toType)
}.toList
val listApply = Select(reify(List).tree, newTermName("apply"))
c.Expr[List[Any]](Apply(listApply, types))
}
}
This will give you a list of type tags, not symbols, but you can pretty easily get the symbols, either like this:
scala> PackageMacros.allTypeSymbols("foo").map(_.tpe.typeSymbol) foreach println
class Baz$
class Bar
class Baz
trait FooTrait
class Bar$
Or in the macro itself.
I have a macro which enumerates the direct sub types of a sealed trait:
import scala.reflect.macros.Context
import language.experimental.macros
object Checker {
def apply[A]: Unit = macro applyImpl[A]
def applyImpl[A: c.WeakTypeTag](c: Context): c.Expr[Unit] = {
val tpe = c.weakTypeOf[A].typeSymbol.asClass
require (tpe.isSealed)
tpe.typeSignature // SI-7046
require (tpe.knownDirectSubclasses.nonEmpty)
import c.universe._
c.Expr[Unit](reify {} .tree)
}
}
Then this works:
sealed trait A
case class A1(i: Int) extends A
object NotNested {
val nada = Checker[A]
}
But this fails:
object Nested {
sealed trait A
case class A1(i: Int) extends A
val nada = Checker[A]
}
[error] java.lang.IllegalArgumentException: requirement failed:
Did not find sub classes
I thought I ran into SI-7046, so I added the call to tpe.typeSignature, but that doesn't help apparently.
I need a work around for this using Scala 2.10.2. Somehow I must enforce some extra type trees to be initialised, I guess?
A possible workaround is following (what do you think #EugeneBurmako).
val cls: ClassSymbol = sub.asClass
println(s"knownDirectSubclasses = ${cls.knownDirectSubclasses}")
// print "knownDirectSubclasses = Set()"
val subsub = cls.owner.typeSignature.decls.filter {
case c: ClassSymbol =>
cls != c && c.selfType.baseClasses.contains(cls)
case _ => false
}
println(s"subsub = $subsub")
// print the actual sub classes
I tried to simplify the creation of ASTs, but got a weird error message:
case class Box(i: Int)
object M {
import language.experimental.macros
import scala.reflect.makro.Context
case class meth(obj: String, method: String)(implicit val c: Context) {
import c.universe._
def apply(xs: Tree*) =
Apply(Select(Ident(obj), newTermName(method)), xs.toList)
}
def box(n: Int): Box = macro boxImpl
def boxImpl(c: Context)(n: c.Expr[Int]): c.Expr[Box] = {
import c.universe._
implicit val cc: c.type = c
n.tree match {
case arg # Literal(Constant(_)) =>
meth("Box", "apply").apply(arg)
}
}
}
Error:
<console>:26: error: type mismatch;
found : c.universe.Literal
required: _2.c.universe.Tree where val _2: M.meth
possible cause: missing arguments for method or constructor
meth("Box", "apply").apply(arg)
^
Is it possible to infer the correct types into class meth? Or is there a workaround for the problem?
EDIT: Based on #retronyms answer I got this to work:
object M {
import language.experimental.macros
import scala.reflect.makro.Context
def meth(implicit c: Context) = new Meth[c.type](c)
class Meth[C <: Context](val c: C) {
import c.universe._
def apply(obj: String, method: String, xs: Tree*) =
Apply(Select(Ident(obj), newTermName(method)), xs.toList)
}
def box(n: Int): Box = macro boxImpl
def boxImpl(c: Context)(n: c.Expr[Int]): c.Expr[Box] = {
import c.universe._
implicit val cc: c.type = c
n.tree match {
case arg # Literal(Constant(_)) =>
c.Expr(meth.apply("Box", "apply", arg))
}
}
}
Constructors are not currently allowed to have dependent method types (SI-5712). It's on the radar to be fixed, hopefully for 2.10.1 or 2.11.
In the meantime, you can follow the pattern I used in macrocosm to reuse code within macro implementations.