2013-08-26 15 views
6

Chcę zaimplementować makro Scala, które ma funkcję częściową, wykonuje pewne przekształcenia na wzorach funkcji, a następnie stosuje ją do danego wyrażenia.Jak przekształcić i zastosować funkcję częściową za pomocą makr Scala?

Aby to zrobić, zacząłem z następującego kodu:

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)") 
} 

I dekonstrukcji funkcję częściową, wybierz definicje przypadku funkcji applyOrElse wykonać żądaną przemianę na każdej definicji, i postawił wszystko z powrotem razem . Makro jest wywoływane w ten sposób:

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

Niestety, to nie działa zgodnie z oczekiwaniami. Korzystanie z makro w prosty przykład

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

wyników w następującym komunikatem o błędzie:

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

Jest to nieco mylące, ponieważ drukowanie wygenerowanym częściowy plony funkcja

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

który wyraźnie definiuje metodę isDefinedAt.

Czy ktoś ma pomysł, jaki jest tutaj problem i jak to zrobić?

+1

To nie jest odpowiedź na najciekawszą część twojego pytania, ale czy próbowałeś używać ['Transformer'] (http://www.scala-lang.org/api/current/index.html#scala.reflect .api.Trees $ Transformer)? Powinno [robić to, co chcesz] (https://gist.github.com/travisbrown/6340753), prawdopodobnie bardziej niezawodnie. –

+0

@TravisBrown Klasa Transformer jest dokładnie tym, czego szukałem, dziękuję za podpowiedź. Nie krępuj się, aby podać swój punkt widzenia jako odpowiedź. –

Odpowiedz

4

Nowy odbicie API zapewnia klasę Transformer który jest specjalnie zaprojektowany, aby pomóc z tego rodzaju transformacji drzewa:

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 
} 

możesz potrzebować dodatkowych maszyn, aby upewnić się, że transformacja jest tylko stosowane w przypadku list, na które ma być zastosowany, ale ogólnie takie podejście będzie bardziej niezawodne niż ręczne przekształcenie drzewa.

Wciąż jestem ciekawy, dlaczego twoja wersja nie działa, a jeśli masz czas, warto umieścić razem zredukowany przykład na inne pytanie tutaj.

+0

Przypuszczam, że istnieje pewien nieprzyjemny efekt uboczny polegający na mieszaniu drzew wpisanych z literą i bez typów w rozszerzeniu makr. Synteza częściowej funkcji wywołuje sporą część magii, która może grzebać w obecności częściowo wpisanych danych wejściowych. –

Powiązane problemy