2012-08-02 10 views
9

Estoy tratando de crear algo de abstracción similar a SQL y me he topado con un problema.¿Cuál es la forma recomendada de pasar los resultados de macrocomputaciones al tiempo de ejecución?

Ésta es una “tabla de base de datos” simplificada:

trait Coffee { 
    def id: Long 
    def name: String 
    def brand: String 
} 

Ésta es mi consulta abstracción:

import language.experimental.macros 

object Query { 
    def from[T] = 
    macro QueryMacros.fromMacro[T] 
} 

class From[T] { 
    def select[S](s: T => S): Select[T] = 
    macro QueryMacros.selectMacro[T, S] 
} 

class Select[T] { 
    def where(pred: T => Boolean): Where = 
    macro QueryMacros.whereMacro[T] 
} 

class Where(val result: String) 

Ésta es mi macro aplicación:

import scala.reflect.macros.Context 

object QueryMacros { 
    val result = new StringBuilder 

    def fromMacro[T : c.WeakTypeTag](c: Context): c.Expr[From[T]] = { 
    result ++= ("FROM " + c.weakTypeOf[T]) 
    c.universe.reify(new From[T]) 
    } 

    def selectMacro[T : c.WeakTypeTag, S : c.WeakTypeTag](c: Context)(s: c.Expr[T => S]): c.Expr[Select[T]] = { 
    result ++= ("SELECT " + s.tree) 
    c.universe.reify(new Select[T]) 
    } 

    def whereMacro[S](c: Context)(pred: c.Expr[S]): c.Expr[Where] = { 
    result ++= ("WHERE " + pred.tree) 
    c.universe.reify(new Where(result.toString)) 
    } 
} 

Y esta es mi código de ejemplo:

object Main extends App { 
    println("Query start") 
    val query = 
    Query.from[Coffee] 
     .select(_.id) 
     .where(_.brand == "FairTrade") 

    println(query.result) 
    println("Query end") 
} 

compila y funciona muy bien, pero la salida es:

Query start 

Query end 

Básicamente, result parece estar vacío. Esperaba que mantuviera las cuerdas acumuladas de los árboles.

¿Cómo puedo pasar mis datos de la etapa de compilación de macros a la etapa siguiente, para que se muestren en el tiempo de ejecución? Por supuesto, podría pasar la cadena actual al siguiente método explícitamente, pero me gustaría evitar eso.

Respuesta

3

Básicamente es necesario tener una abstracción Queryable que: 1) proporciona la API de recogida (from, select, etc.), 2) recuerda los métodos que se llamaban en él reificando las llamadas y acumulando en su interior.

Este concepto se explica un poco en nuestras diapositivas de ScalaDays [1] y se implementa en Slick (que es de código abierto) [2]. Por cierto, en LINQ hacen más o menos lo mismo con los métodos en Queryable que reifican las llamadas y las alimentan a su objeto que implementa IQueryable, p. como se describe en [3].

Enlaces:

  1. http://scalamacros.org/talks/2012-04-18-ScalaDays2012.pdf
  2. https://github.com/slick/slick/tree/master/src/main/scala/scala/slick/queryable
  3. http://community.bartdesmet.net/blogs/bart/archive/2007/04/06/the-iqueryable-tales-linq-to-ldap-part-1-key-concepts.aspx
+0

Hola Eugene, gracias, echaré un vistazo. Parece que hay una solución verdadera. :-) – soc

2

El problema no es pasar la información de una macro llamada a la siguiente. Todo eso ocurre en tiempo de compilación, así que debería funcionar. El problema es con la macro llamada última. Como devuelve c.universe.reify(new Where(result.toString)), se llama al new Where(result.toString) en tiempo de ejecución. Y luego result estará vacío. Lo que puede hacer es devolver c.Expr(tree), donde tree aplica el constructor Where a un literal String que contiene result.toString.

Además, debe tener en cuenta que su código depende del orden en que se compilan las llamadas a macro. Si tiene varias llamadas a estas macros en varios archivos de código, es posible que result contenga información de llamadas anteriores. Probablemente sea mejor reconsiderar todo tu enfoque.

+0

Sí, era solo un prototipo para evitar el hecho de que quería imprimir los árboles en tiempo de compilación, pero no pude hacer que Eclipse me mostrara la salida en tiempo de compilación. :-) ¿Tiene una idea de una mejor solución? – soc

+0

¿Te refieres a esto? http://stackoverflow.com/questions/11677609/how-do-i-print-an-expanded-macro-in-scala –

0

Como señala @Kim la agregación de la información no es el problema, sino que la expansión de la macro generará código que evalúa result.toString en tiempo de ejecución cuando de hecho está vacío.He tenido un problema similar al de usted y terminé haciendo el equivalente reemplazando result.toString con resultExpr(c).splice

private def resultExpr(c :Context) = { 
    import c.universe._ 
    c.Expr[String](Literal(Constant(result.toString))) 
} 

(Como @Kim también señala que esto alimentar a los resultados acumulados de todas las llamadas macro de nuevo en el tiempo de ejecución, así que ten cuidado!)

Cuestiones relacionadas