How to convert and apply a partial function using Scala macros?

I want to implement a Scala macro that takes a partial function, performs some transformations in function templates, and then applies it to this expression.

To do this, I started with the following code:

def myMatchImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: Context)(expr: c.Expr[A])(patterns: c.Expr[PartialFunction[A, B]]): c.Expr[B] = { import c.universe._ /* * Deconstruct the partial function and select the relevant case definitions. * * A partial, anonymus function will be translated into a new class of the following form: * * { @SerialVersionUID(0) final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[A,B] with Serializable { * * def <init>(): anonymous class $anonfun = ... * * final override def applyOrElse[...](x1: ..., default: ...): ... = ... match { * case ... => ... * case (defaultCase$ @ _) => default.apply(x1) * } * * def isDefined ... * } * new $anonfun() * }: PartialFunction[A,B] * */ val Typed(Block(List(ClassDef(a, b, x, Template(d, e, List(f, DefDef(g, h, i, j, k, Match(l, allCaseDefs)), m)))), n), o) = patterns.tree /* Perform transformation on all cases */ val transformedCaseDefs: List[CaseDef] = allCaseDefs map { case caseDef => caseDef // This code will perform the desired transformations, now it just identity } /* Construct anonymus partial function with transformed case patterns */ val result = Typed(Block(List(ClassDef(a, b, x, Template(d, e, List(f, DefDef(g, h, i, j, k, Match(l, transformedCaseDefs)), m)))), n), o) // println(show(result)) c.Expr[B](q"$result($expr)") } 

I deconstruct the partial function, select the applyOrElse function case definitions, perform the required transformation on each definition, and add everything together. The macro is invoked as follows:

 def myMatch[A, B](expr: A)(patterns: PartialFunction[A, B]): B = macro myMatchImpl[A,B] 

Unfortunately, this does not work properly. Using a macro in a simple example

 def test(x: Option[Int]) = myMatch(x){ case Some(n) => n case None => 0 } 

The following error message appears:

 object creation impossible, since method isDefinedAt in trait PartialFunction of type (x: Option[Int])Boolean is not defined 

This is a bit confusing since printing the created partial function gives

 ({ final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[Option[Int],Int] with Serializable { def <init>(): anonymous class $anonfun = { $anonfun.super.<init>(); () }; final override def applyOrElse[A1 <: Option[Int], B1 >: Int](x2: A1, default: A1 => B1): B1 = ((x2.asInstanceOf[Option[Int]]: Option[Int]): Option[Int] @unchecked) match { case (x: Int)Some[Int]((n @ _)) => n case scala.None => 0 }; final def isDefinedAt(x2: Option[Int]): Boolean = ((x2.asInstanceOf[Option[Int]]: Option[Int]): Option[Int] @unchecked) match { case (x: Int)Some[Int]((n @ _)) => true case scala.None => true case (defaultCase$ @ _) => false } }; new $anonfun() }: PartialFunction[Option[Int],Int]) 

which clearly defines the isDefinedAt method.

Does anyone have an idea what the problem is here and how to do it?

+6
source share
1 answer

The new reflection API provides the Transformer class, specifically designed to help with this type of tree transformation:

 import scala.language.experimental.macros import scala.reflect.macros.Context def myMatchImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: Context)( expr: c.Expr[A] )( patterns: c.Expr[PartialFunction[A, B]] ): c.Expr[B] = { import c.universe._ val transformer = new Transformer { override def transformCaseDefs(trees: List[CaseDef]) = trees.map { case caseDef => caseDef } } c.Expr[B](q"${transformer.transform(patterns.tree)}($expr)") } def myMatch[A, B](expr: A)(patterns: PartialFunction[A, B]): B = macro myMatchImpl[A,B] def test(x: Option[Int]) = myMatch(x) { case Some(n) => n case None => 0 } 

You may need some additional equipment to make sure that the conversion only applies to the to-do lists that you want to apply, but overall this approach will be more reliable than manually converting the tree.

I'm still wondering why your version is not working, and if you have the time, maybe you should put together the example below for another question.

+4
source

Source: https://habr.com/ru/post/952493/


All Articles