2013-08-26 7 views
6

Ich möchte ein Scala-Makro implementieren, das eine Teilfunktion übernimmt, einige Transformationen an den Mustern der Funktion durchführt und sie dann auf einen bestimmten Ausdruck anwendet.Wie kann man eine Teilfunktion mit Scala-Makros transformieren und anwenden?

so zu tun, begann ich mit dem folgenden 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's 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)") 
} 

ich die Teilfunktion dekonstruieren, wählen Sie die Falldefinitionen der applyOrElse Funktion, führen Sie die auf jeder Definition gewünschte Transformation, und alles zusammen zurück . Das Makro wird wie folgt aufgerufen:

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

Leider funktioniert dies nicht wie erwartet. Unter Verwendung des Makro in einem einfachen Beispiel

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

Ergebnisse in der folgenden Fehlermeldung:

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

Dieses etwas verwirrend ist, da das Drucken der erzeugten Teilfunktion Ausbeuten

({ 
    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]) 

welche eindeutig definiert die isDefinedAt-Methode.

Hat jemand eine Idee, was ist das Problem hier und wie geht das richtig?

+1

Es ist keine Antwort auf den interessantesten Teil Ihrer Frage, aber haben Sie versucht, ['Transformer'] (http://www.scala-lang.org/api/current/index.html#scala.reflect .api.Trees $ Transformer)? Es sollte [tun, was Sie wollen] (https://gist.github.com/travisbrown/6340753), wahrscheinlich robuster. –

+0

@TravisBrown Die Transformer-Klasse ist genau das, was ich gesucht habe, danke für den Hinweis. Fühlen Sie sich frei, Ihren Kern als Antwort zu posten. –

Antwort

4

Die neue Reflection-API bietet eine Transformer Klasse, die mit dieser Art von Baum Transformation zu helfen, die speziell entwickelt wurde:

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 
} 

Sie können einige zusätzliche Maschinen müssen sicherstellen, dass die Transformation nur auf den Fall angewendet werden, Listen, auf die sie angewendet werden sollen, aber im Allgemeinen wird dieser Ansatz robuster sein, als den Baum manuell zu transformieren.

Ich bin immer noch neugierig, warum Ihre Version nicht funktioniert, und wenn Sie die Zeit haben, könnte es sich lohnen, hier ein reduziertes Beispiel für eine andere Frage zusammenzustellen.

+0

Ich vermute, dass es einen unangenehmen Nebeneffekt hat, typisierte und untypisierte Bäume in einer Makroexpansion zu mischen. Die Partial-Function-Synthese ruft einen fairen Anteil an Magie hervor, die in Gegenwart von teilweise typisierten Eingaben fummeln könnte. –