2012-12-10 7 views
12

pracuję z Scala Makra i mają następujący kod w makro: Jak używać typu obliczonego w makrze Scala w klauzuli reify?

val fieldMemberType = fieldMember.typeSignatureIn(objectType) match { 
     case NullaryMethodType(tpe) => tpe 
     case _      => doesntCompile(s"$propertyName isn't a field, it must be another thing") 
    } 

    reify{ 
     new TypeBuilder() { 
     type fieldType = fieldMemberType.type 
     } 
    } 

Jak widać, udało mi się dostać c.universe.Type fieldMemberType. Reprezentuje typ określonego pola w obiekcie. Po otrzymaniu tego chcę utworzyć nowy obiekt TypeBuilder w reifikacji. TypeBuilder to klasa abstrakcyjna z parametrem abstrakcyjnym. Ten abstrakcyjny parametr to fieldType. Chcę, aby ten fieldType był typem, który znalazłem wcześniej.

Uruchomienie kodu pokazanego tutaj zwraca mi fieldMemberType not found. Czy istnieje sposób, aby uzyskać fieldMemberType do pracy w klauzuli reify?

Odpowiedz

24

Problem polega na tym, że kod przekazywany pod numer reify jest zasadniczo umieszczany dosłownie w punkcie, w którym makro jest rozwijane, a fieldMemberType nic tam nie znaczy.

W niektórych przypadkach można użyć splice, aby wkraść wyrażenie, które masz w czasie ekspansji makr do kodu, który korygujesz. Na przykład, jeśli staraliśmy się stworzyć instancję tej cechy:

trait Foo { def i: Int } 

i miał tę zmienną w czasie makro-rozbudowa:

val myInt = 10 

Moglibyśmy napisać następujący:

reify { new Foo { def i = c.literal(myInt).splice } } 

To nie zadziała tutaj, co oznacza, że ​​będziesz musiał zapomnieć o miłym małym reify i napisać AST ręcznie. Niestety często się to zdarza. Moja średnia podejście jest, aby rozpocząć nową rEPL i wpisać coś takiego:

import scala.reflect.runtime.universe._ 

trait TypeBuilder { type fieldType } 

showRaw(reify(new TypeBuilder { type fieldType = String })) 

Będzie to wypluć kilka linii AST, które można następnie wyciąć i wkleić do swojej definicji makra jako punkt wyjścia. Potem bawić się z nim, zastępując rzeczy tak:

Ident(TypeBuilder) 

z tym:

Ident(newTypeName("TypeBuilder")) 

And FINAL z Flag.FINAL, i tak dalej. Chciałbym, aby metody typu AST odpowiadały dokładniej kodowi potrzebnemu do ich zbudowania, ale szybko zorientujesz się, co musisz zmienić. Będziesz skończyć z czymś takim:

c.Expr(
    Block(
    ClassDef(
     Modifiers(Flag.FINAL), 
     anon, 
     Nil, 
     Template(
     Ident(newTypeName("TypeBuilder")) :: Nil, 
     emptyValDef, 
     List(
      constructor(c), 
      TypeDef(
      Modifiers(), 
      newTypeName("fieldType"), 
      Nil, 
      TypeTree(fieldMemberType) 
     ) 
     ) 
    ) 
    ), 
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil) 
) 
) 

Gdzie anon jest już utworzony z góry za anonimową klasy nazwą typu i constructor jest metoda wygoda używam do tego typu rzeczy trochę mniej odrażający (możesz znaleźć jego definicję pod koniec this complete working example).

Teraz jeśli to wyrażenie owinąć w coś jak this, możemy napisać następujące:

scala> TypeMemberExample.builderWithType[String] 
res0: TypeBuilder{type fieldType = String} = [email protected] 

Tak to działa.Wzięliśmy c.universe.Type (który dostaję tutaj z WeakTypeTag parametru typu na builderWithType, ale będzie działać dokładnie w ten sam sposób z każdym starym Type) i użył go do zdefiniowania elementu typu naszej cechy TypeBuilder.

+0

Tylko jedno pytanie, w jaki sposób "newTypeName" znajduje typ? Chodzi mi o to, skąd on wie, w jakim to jest pakiecie? – mgonto

+0

Dzięki! Natomiast 'newTypeName' nie wyszukuje samego typu - po prostu kopiuje napis, który podajesz do kodu generowanego przez makro, który zostanie normalnie sprawdzony przez kompilator. Jeśli chcesz określić pakiet, możesz napisać np. 'Wybierz (Ident (" pakiet "), newTypeName (" MyClass "))'. –

+3

Możesz użyć '-Yreify-copypaste' i/lub' showRaw', aby uzyskać kod budujący fragmenty kodu, które Cię interesują. –

6

Istnieje prostsze podejście niż pisanie drzewa w przypadku użycia. Rzeczywiście używam go cały czas, by trzymać drzewa w ukryciu, ponieważ programowanie z drzewami może być naprawdę trudne. Wolę obliczać typy i używać reify do generowania drzew. Zapewnia to znacznie solidniejsze i "higieniczne" makra oraz mniejszy czas kompilacji. IMO używające drzew musi być ostatnią deską ratunku, tylko dla kilku przypadków, takich jak transformacje drzew lub ogólne programowanie dla rodziny typów, takich jak krotki.

Wskazówką tutaj jest zdefiniowanie funkcji przyjmującej parametry typu, typy, których chcesz użyć w obiekcie reify, z kontekstem związanym z WeakTypeTag. Następnie wywołuje się tę funkcję, przekazując wyraźnie WeakTypeTags, które można zbudować z typów wszechświatowych dzięki metodzie WeakTypeTag z kontekstu.

W twoim przypadku dałoby to następujące.

val fieldMemberType: Type = fieldMember.typeSignatureIn(objectType) match { 
     case NullaryMethodType(tpe) => tpe 
     case _      => doesntCompile(s"$propertyName isn't a field, it must be   another thing") 
    } 

    def genRes[T: WeakTypeTag] = reify{ 
    new TypeBuilder() { 
     type fieldType = T 
    } 
    } 

    genRes(c.WeakTypeTag(fieldMemberType)) 
+0

To jest znacznie czystsze podejście. Dzięki – mgonto

+0

+1 (i powinienem był zauważyć, że to podejście działa tutaj), ale z mojego doświadczenia wynika, że ​​istnieje wiele przypadków (może większość), w których bezpośrednia praca z drzewami jest nieunikniona - w szczególności w dowolnym momencie, do którego należy się odwołać (lub create) metody lub klasy według ich nazw jako łańcuchów. Na przykład [tutaj] (https://github.com/travisbrown/rillit), [tutaj] (http://stackoverflow.com/a/13335741/334519), [tutaj] (http://stackoverflow.com/a/13447439/334519) itp. –

Powiązane problemy