2012-04-13 12 views
8

Kiedy próbuję budować wewnętrzne DSL w Scali, napotykam na wspólny problem i nie jestem w stanie stworzyć rozwiązania. Aby dokonać rzeczy wyglądają trochę bardziej jak typowy język, chciałbym składnia wyglądać mniej więcej tak:Skonstruuj listę z serii wyrażeń w Scali

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

W praktyce istnieje kilka problemów. Pierwszym problemem jest pobranie obiektu model, który pobiera w ten sposób dwa argumenty. Jeśli ktokolwiek ma jakieś pomysły, daj mi znać. Ale to, co zrobiłem, a nie jest coś zrobić nieco więcej tak:

model('Foo) { 
    ... 
} 

Jeżeli model jest teraz funkcja, która następnie zwraca obiekt z metody apply który następnie zużywa lambda, który następuje. Z którym mogę żyć. Mógłbym żyć z podobnym problemem wewnątrz lambda, więc rzeczy takie jak decl 'Real 'x lub w środku. Ale chcę uzyskać wyniki wszystkich tych wyrażeń w nawiasach klamrowych, aby uzyskać "zwrócony" jako listę. Innymi słowy, jest to, co chcę napisać coś takiego:

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

gdzie decl(...) ocenia coś typu Declaration a {...} następnie ocenia się List[Declaration]. Podejrzewam, że jest jakiś sposób użycia implicite, aby to zrobić, ale nie byłem w stanie go znaleźć. Krótko mówiąc, chciałbym, aby:

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

... ocenić do równowartości ...

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

uwagi lub sugestie?

Odpowiedz

4

Jako pierwszy pomysł, można spróbować listy zmiennych argumentów, które pozwala używać przecinków zamiast średników:

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

Alternatywnie, można przedłużyć trait pozbyć niektórych nawiasów i przecinki:

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

Tak, widziałem to podejście z niektórymi deklaratywnymi interfejsami DSL GUI. Zgadzam się, że jest blisko. Miałem tylko nadzieję, że nie będę musiał() ogarnąć całej sprawy, a potrzeba użycia "," jest problematyczna, ponieważ za każdym razem, gdy chcesz dodać lub usunąć, musisz martwić się o to, że "między", ale nie na końcu. –

+0

Zmodyfikowalem moją odpowiedź, aby odpowiedzieć na Twój komentarz. – paradigmatic

+0

Ah, bardzo sprytny. Używanie składni konstruktora i metod zdefiniowanych lokalnie. Podoba mi się i myślę, że to może zadziałać. W rzeczywistości mogę go uprościć do "nowego modelu (" Foo) {...} ", który przycina kod. Miły sposób na wykorzystanie faktu, że squiggly nawiasy klamrowe w kontekście konstruktora pozwalają na łatwe wprowadzanie rzeczy w tym zakresie. Zastanawiam się, czy makra Scala 2.10 sprawią, że będzie to jeszcze prostsze? –

2

OK, całkowicie zmieniono to po tym, jak zdałem sobie sprawę, że 'Foo ma być nazwą modelu.

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

Następnie przy użyciu internetowej:

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

Więc sztuczek oto

1) za pomocą zmiennej do śledzenia aktualnego modelu

2) z zastosowaniem - symbole dla nazw metod (możesz zamiast tego użyć apply, jeśli wolisz nawiasy)

3) za pomocą konstruktora tak, że wrócił klasa może być niezmienna

Chociaż TBH to może być trochę za dużo, żeby uniknąć pewnych przecinki ... :)

4

O Boże, co ja zrobiłem?

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

Gratulujemy świetnej odpowiedzi! Sądzę, że właśnie po to jest 'Dynamic'. –

Powiązane problemy