2015-09-17 3 views
1

Я пытаюсь написать макрос, который может использовать информацию о полях класса для создания схемы. Например, допустим, у меня есть класс под названием SchemaWriter[T], для которого я хочу генерировать реализации с использованием макроса.Scala macro lift symbol или types

trait SchemaWriter[T] { 
    def schema: org.bibble.MsonSchema 
} 

case class MsonSchema(fields: Seq[MsonType]) 
case class MsonType(name: String, `type`: Class[_]) // might want other stuff here too, derived from a symbol or type-signature 
object MsonType { 
    def apply(symbol:Symbol): MsonType = ... 
} 

Идея заключается в том, что мой макрос будет выплюнуть код, подобный:

class FooSchemaWriter extends SchemaWriter[Foo] { 
    def schema : org.bibble.MsonSchema= { 
    val fields = for (symbol <- fields of class) yield { 
    MsonType(symbol) 
    } 
    org.bibble.MsonSchema(fields) 
} 
} 

можно реализовать макрос, такие как:

object Macros { 

    def writerImpl[T: c.WeakTypeTag](c: Context): c.Expr[SchemaWriter[T]] = { 

    import c.universe._ 
    val T = weakTypeOf[T] 

    val fields = T.declarations.collectFirst { 
     case m: MethodSymbol if m.isPrimaryConstructor => m 
    }.get.paramss.head 

    val fieldTrees: [Tree] = fields.map { f => 
     q"""org.bibble.MsonType(${f})""" 
    } 

    c.Expr[SchemaWriter[T]](q""" 
     new SchemaWriter[$T] { 
     def schema = { 
     val fields = Seq(..$fieldTrees) 
     org.bibble.MsonSchema(fields) 
     } 
     } 
    """) 
    } 
} 

Но создание qqs для полей причин либо допустимой ошибкой, либо ошибкой без кавычек. Я задал аналогичный вопрос вчера Scala macros Type or Symbol lifted, который получил фантастический ответ, но моя реализация не может быть ограничена передачей строк, поскольку мне нужно больше информации о типе, чтобы генерировать детали для схемы, где я думаю, что моя путаница лежит.

+0

Практически невозможно помочь, не зная определения «Поле». В частности, что такое подпись конструктора «Поле». Вообще говоря, при размещении вопроса вы должны попытаться опубликовать [минимальный, полный и проверяемый пример] (http://stackoverflow.com/help/mcve). Да, я знаю, что вы не можете опубликовать пример компиляции, учитывая, что вы точно просите, как исправить ваш код, но, по крайней мере, предоставить все необходимые зависимости. –

+0

Я уточню правильное определение схемы и поля – monkjack

+0

Обновлено для конкретного типа схемы. Что касается этой схемы Mson, это не ключевой бит, но если я смогу получить то, что сейчас есть, чтобы скомпилировать, остальное должно быть легко. – monkjack

ответ

1

Я думаю, что ваше основное замешательство состоит в том, что вы, похоже, не полностью понимаете, как обрабатываются макросы. Это полностью время компиляции. Когда ваш макрос работает, он имеет доступ к информации о скомпилированной программе, включая Symbol. Затем макрос генерирует некоторый код, который станет частью вашей программы, но сам сгенерированный код не имеет доступа к Symbol или что-либо еще, к которому имел доступ макрос.

Так MsonType.apply в вашем примере не будет никакой пользы, потому что вход является Symbol, который доступен в макрос только (во время компиляции), и выход является MsonSchema, что вам нужно в время выполнения. Что бы иметь смысл, чтобы изменить тип возвращаемого из MsonType в c.Expr[MsonType] (или просто c.universe.Tree), или другими словами MsonType.apply бы сейчас взять символ и возвращает дерево, представляющее экземпляр MsonType (в отличие от возвращения в MsonType экземпляр), и ваш макрос может затем вызвать это и включить его в окончательное дерево, возвращаемое компилятору (который компилятор затем включит в вашу программу на сайте вызова). В этом конкретном случае, я думаю, что лучше всего удалить MsonType.apply вообще и реализовать преобразование от Symbol до c.universe.Tree прямо в макросе writerImpl.

Это преобразование на самом деле очень просто. Все, что вам нужно построить MsonType это имя поля, а Class экземпляр, так что вы идете:

q"""org.bibble.MsonType(${f.name.decoded}, classOf[${f.typeSignature}])""" 

Для поля foo типа Bar это будет генерировать дерево, представляющее следующее выражение:

org.bibble.MsonType("foo", classOf[Bar]) 

Так оно и есть, мы должны идти.

Ниже вы найдете полную реализацию. Обратите внимание, что я взял на себя смелость изменить определение типов вашей схемы таким образом, что это намного логичнее и универсальнее для меня. И, в частности, каждое поле теперь имеет связанную с ним схему (учитывая ваш предыдущий вопрос, это явно то, что вы хотели в первую очередь).

scala> :paste 
// Entering paste mode (ctrl-D to finish) 

import scala.language.experimental.macros 
import scala.reflect.macros._ 

case class MsonSchema(`type`: Class[_], fields: Seq[MsonField]) 
case class MsonField(name: String, schema: MsonSchema) 
trait SchemaWriter[T] { 
    def schema: MsonSchema 
} 
object SchemaWriter { 
    def apply[T:SchemaWriter]: SchemaWriter[T] = implicitly 
    implicit def defaultWriter[T] = macro Macros.writerImpl[T] 
} 
object Macros { 
    def writerImpl[T: c.WeakTypeTag](c: Context): c.Expr[SchemaWriter[T]] = { 
    import c.universe._ 
    c.Expr[SchemaWriter[T]](generateSchemaWriterTree(c)(weakTypeOf[T])) 
    } 
    private def generateSchemaWriterTree(c: Context)(T: c.universe.Type): c.universe.Tree = { 
    import c.universe._ 
    val fields = T.declarations.collectFirst { 
     case m: MethodSymbol if m.isPrimaryConstructor => m 
    }.get.paramss.head 

    val MsonFieldSym = typeOf[MsonField].typeSymbol 
    val SchemaWriterSym = typeOf[SchemaWriter[_]].typeSymbol 
    val fieldTrees: Seq[Tree] = fields.map { f => 
     q"""new $MsonFieldSym(
     ${f.name.decoded}, 
     _root_.scala.Predef.implicitly[$SchemaWriterSym[${f.typeSignature}]].schema 
    )""" 
    } 

    c.resetLocalAttrs(q""" 
     new $SchemaWriterSym[$T] { 
     val schema = MsonSchema(classOf[$T], Seq(..$fieldTrees)) 
     } 
    """) 
    } 
} 

// Exiting paste mode, now interpreting. 

warning: there were 7 deprecation warnings; re-run with -deprecation for details 
warning: there was one feature warning; re-run with -feature for details 
import scala.language.experimental.macros 
import scala.reflect.macros._ 
defined class MsonSchema 
defined class MsonField 
defined trait SchemaWriter 
defined object SchemaWriter 
defined object Macros 

scala> case class Foo(ab: String, cd: Int) 
defined class Foo 

scala> SchemaWriter[Foo].schema 
res0: MsonSchema = MsonSchema(class Foo,List(MsonField(ab,MsonSchema(class java.lang.String,List())), MsonField(cd,MsonSchema(int,List())))) 
Смежные вопросы