2012-04-13 11 views
8

Quando provo a creare DSL interni in Scala, mi imbatto in un problema comune e non sono stato in grado di creare una soluzione. Per rendere le cose sembrano un po 'più simile a un linguaggio tipico, mi piacerebbe la sintassi a guardare qualcosa di simile:Costruire una lista da una serie di espressioni in Scala

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

In pratica, ci sono diversi problemi. Il primo problema è ottenere un oggetto model qui per prendere due argomenti in questo modo. Se qualcuno ha qualche idea, fammi sapere. Ma quello che ho fatto, invece è quello di fare qualcosa di un po 'più simile a questo:

model('Foo) { 
    ... 
} 

Dove modello ora è una funzione che restituisce un oggetto con un metodo che poi apply consuma lambda che segue. Con cui posso convivere Potrei vivere con un problema simile all'interno del lambda, quindi cose come decl 'Real 'x o decl('Real,'x) all'interno. Ma quello che voglio fare è ottenere i risultati di tutte quelle espressioni all'interno delle parentesi ondulate per ottenere "restituito" come lista. In altre parole, quello che voglio è quello di scrivere qualcosa di simile:

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

dove decl(...) viene valutato come qualcosa di tipo Declaration e il {...} viene valutata poi a List[Declaration]. Sospetto che ci sia un modo di usare impliciti per farlo, ma non sono stato in grado di trovarlo. In breve, mi piacerebbe fare:

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

... valutare l'equivalente di ...

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

commenti o suggerimenti?

risposta

4

Come prima idea, si potrebbe provare argomenti elenchi di variabili, che consente di utilizzare le virgole invece dei punti e virgola:

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

In alternativa, è possibile estendere una trait per sbarazzarsi di alcune parentesi e di le virgole:

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ì, ho visto questo approccio anche con alcune delle DSL dichiarative della GUI. Sono d'accordo che questo è vicino. Speravo semplicemente di non dover introdurre() attorno a tutto e la necessità di usare "," è problematico perché ogni volta che vuoi aggiungere o rimuovere, devi preoccuparti di avere "," s tra le cose, ma non alla fine. –

+0

Ho modificato la mia risposta per rispondere al tuo commento. – paradigmatic

+0

Ah, molto intelligente. Utilizzando la sintassi del costruttore e i metodi definiti localmente. Mi piace e penso che potrebbe funzionare. In effetti, posso semplificarlo fino a "nuovo modello ('Foo) {...}" che elimina il codice. Bel modo di sfruttare il fatto che le parentesi ondulate in un contesto di costruzione ti permettono di introdurre facilmente le cose in quel campo. Mi chiedo se i macro di Scala 2.10 lo renderanno ancora più semplice? –

2

OK, completamente rivisti questo dopo aver realizzato che 'Foo si suppone essere il nome del modello.

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

Poi a uso loco:

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

Così gli espedienti qui sono

1) utilizzando una variabile per tenere traccia del modello attuale

2) utilizzando - simboli per i nomi dei metodi (potresti invece usare apply se preferisci parentesi)

3) utilizzando un costruttore in modo che la classe restituito può essere immutabili

Anche se, TBH questo potrebbe essere un po 'troppo solo per evitare alcune virgole ... :)

4

Oh Dio cosa ho fatto?

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

Congratulazioni per un'ottima risposta! Immagino che questo sia solo il significato di 'Dynamic'. –

Problemi correlati