2012-04-13 15 views
8

Cuando intento crear DSL internas en Scala, me encuentro con un problema común y no he podido crear una solución. Para hacer las cosas un poco más como un lenguaje típico, me gustaría que la sintaxis para buscar algo como esto:Construya una lista a partir de una serie de expresiones en Scala

model 'Foo { 
    decl 'Real 'x; 
    decl 'Real 'y; 
} 

En la práctica, hay varias cuestiones. El primer problema es obtener un objeto model aquí para tomar dos argumentos de esta manera. Si alguien tiene alguna idea, házmelo saber. Pero lo que he hecho en cambio, es hacer algo un poco más como esto:

model('Foo) { 
    ... 
} 

Dónde modelo es ahora una función que devuelve un objeto con un método apply que luego consume la lambda que sigue. Con lo que puedo vivir Podría vivir con un problema similar dentro de la lambda, así que cosas como decl 'Real 'x o decl('Real,'x) en el interior. Pero lo que quiero hacer es obtener los resultados de todas esas expresiones dentro de las llaves onduladas para "devolver" como una lista. En otras palabras, lo que quiero es escribir algo como esto:

model 'Foo { 
    decl('Real,'x); 
    decl('Real,'y); 
} 

donde decl(...) devuelva algo de tipo Declaration y la {...} continuación, se evalúa como List[Declaration]. Sospecho que hay alguna forma de utilizar implícitos para hacer esto, pero no he podido encontrarlo. En resumen, me gustaría hacer:

model 'Foo { 
    decl('Real,'x); 
    decl('Real,'y); 
} 

... se evalúan como el equivalente de ...

model 'Foo { 
    decl('Real,'x) :: 
    decl('Real,'y) :: 
    Nil 
} 

Comentarios o sugerencias?

Respuesta

4

Como primera idea, puede probar con listas de argumentos variables, lo que permite el uso de comas en lugar de puntos y comas:

case class Declaration(name: String) 

def decl(s: String) = Declaration(s) 

case class Model(sym: Symbol, decls: List[Declaration]) 

def model(sym: Symbol)(decls: Declaration*) = 
    Model(sym, decls.toList) 

val m = model('Foo)(
    decl("bar"), 
    decl("baz") 
) 

Como alternativa, puede extender una trait para deshacerse de algunos paréntesis y de las comas:

case class ModelBuilder(sym: Symbol) { 
    def using(decls: Declarations) = Model(sym, decls.toList) 
} 

trait Declarations { 

    protected var decls = List[Declaration]() 

    protected def decl(s: String) = 
decls ::= Declaration(s) 

    def toList = decls 
} 

def model(sym: Symbol) = ModelBuilder(sym) 

model('Foo) using new Declarations { 
    decl("bar") 
    decl("baz") 
} 
+0

Sí, he visto este enfoque con algunas de las DSL de GUI declarativas también. Estoy de acuerdo con que esto está cerca. Esperaba no tener que introducir() todo el asunto y la necesidad de usar "," es problemático porque cada vez que quiera agregar o quitar, debe preocuparse por tener "," s entre las cosas, pero no al final. –

+0

Modifiqué mi respuesta para responder a tu comentario. – paradigmatic

+0

Ah, muy inteligente. Usando la sintaxis del constructor y los métodos definidos localmente. Me gusta y creo que podría funcionar. De hecho, puedo simplificarlo hasta "nuevo modelo ('Foo) {...}" que corta el código. Una buena manera de explotar el hecho de que las llaves onduladas en un contexto constructor te permiten introducir cosas fácilmente en ese ámbito. Me pregunto si las macros Scala 2.10 harán esto aún más simple. –

2

bien, completamente revisados ​​esto después de darse cuenta de que 'Foo se supone que es el nombre del modelo.

trait DSL { 

    private var currentModel: ModelBuilder = null 
    case class Declaration(kind: Symbol, name: Symbol) 
    case class Model(name: Symbol, declarations: List[Declaration]) 
    case class ModelBuilder(name: Symbol, var declarations: Vector[Declaration]) { 
    def -(f: => Unit) = { 
     currentModel = this 
     f 
     Model(name, declarations.toList) 
    } 
    } 

    def decl (s1: Symbol, s2: Symbol) { 
    currentModel.declarations :+= Declaration(s1, s2) 
    } 

    object model { 
    def - (s: Symbol) = ModelBuilder(s, Vector.empty) 
    } 
} 

A continuación, en el uso in situ:

object UseSite extends App with DSL { 

    val m = 

    model - 'Foo - { 
     decl ('Real, 'x) 
     decl ('Real, 'y) 
    } 

    println(m) 
    //Model('Foo,List(Declaration('Real,'x), Declaration('Real,'y))) 
} 

Así los trucos aquí son

1) usando una variable para realizar un seguimiento del modelo actual

2) usando - símbolos para nombres de métodos (en su lugar, podría usar apply si prefiere paréntesis)

3) usando un constructor para que la clase devuelta puede ser inmutables

Aunque, TBH esto podría ser un poco más sólo para evitar algunas comas ... :)

4

dios Oh ¿qué he hecho?

import scala.collection.mutable.ListBuffer 

case class Declaration(t: Symbol, name: Symbol) 
case class Model(name: Symbol, declarations: List[Declaration]) 

object model extends Dynamic { 
    val buffer = ListBuffer.empty[Model] 

    def applyDynamic(name: String)(args: Any*) { 
    buffer += Model(Symbol(name), decl.buffer.toList) 
    decl.buffer.clear() 
    } 
} 

object decl extends Dynamic { 
    val buffer = ListBuffer.empty[Declaration] 

    def applyDynamic(t: String)(args: Any*) { 
    args match { 
     case Seq(name: Symbol) => buffer += Declaration(Symbol(t), name) 
    } 
    } 
} 

model Foo { 
    decl Real 'x 
    decl Real 'y 
} 

assert(model.buffer.head == Model('Foo, List(
    Declaration('Real, 'x), Declaration('Real, 'y)))) 
+0

¡Felicidades por la excelente respuesta! Supongo que esto es para lo que 'Dynamic' es. –

Cuestiones relacionadas