2015-03-27 1 views
3

Я ищу дизайн DSL в Scala с минимальным количеством синтаксиса. Он предназначен для использования пользователями, которые не знают Scala, но могут использовать систему типов Scala для проверки и проверки ошибок. В моей голове DSL выглядит следующим образом:Scala - проектирование DSL с миниатюрным синтаксисом

outer { 
    inner(id = "asdf") { 
     value("v1") 
     value("v2") 
    } 
} 

Этот отрезала должен произвести значение, как это:

Outer(Inner("asdf", Value("v1") :: Value("v2") :: Nil)) 

Указанные структуры данных

case class Outer(inner: Inner) 
case class Inner(values: List[Value]) 
case class Value(value: String) 

Идея заключается в том, что inner функция только доступный в закрытии после outer, value функция доступна только после закрытия после inner, и т. д. То есть следующее не будет компилироваться: outer { value("1") }.

Как я могу реализовать что-то вроде этого? В конце концов, структуры данных не обязательно должны быть неизменными, это может быть что угодно, пока оно строго типизировано.

У меня нет знакомства с макросами Scala, но могу ли я решить эту проблему с помощью макросов?


Ближайший я до сих пор является следующая реализация:

object DSL extends App { 

    def outer = new Outer() 

    class Outer(val values: mutable.MutableList[Inner] = mutable.MutableList.empty) { 
     def inner(id: String): Inner = { 
      val inner = new Inner(id) 
      values += inner 
      inner 
     } 
     def apply(func: Outer => Unit): Outer = { 
      func(this) 
      this 
     } 
     override def toString: String = s"Outer [${values.mkString(", ")}]" 
    } 

    class Inner(val id: String, val values: mutable.MutableList[Value] = mutable.MutableList.empty) { 
     def value(v: String): Value = { 
      val value = new Value(v) 
      values += value 
      value 
     } 
     def apply(func: Inner => Unit): Unit = func(this) 

     override def toString: String = s"Inner (${values.mkString(", ")})" 
    } 

    class Value(val str: String) { 
     override def toString: String = s"Value<$str>" 
    } 

    val value = outer { o => 
     o.inner(id = "some_id") { i => 
      i.value("value1") 
      i.value("value2") 
     } 
    } 

    println(value) 

Как я могу избавиться от анонимных функций аннотаций (т.е. o => и o. и т.д.)?

Альтернативно существует способ лечения outer как new Outer (в этом случае следующий блок кода будет рассматриваться как конструктор, и я смогу вызвать функции-члены)?

ответ

3

Как вы уже заметили, это сводится к

есть способ, чтобы лечить outer, как new Outer

и, к сожалению, ответа нет. Я думаю, это было возможно в экспериментальной Scala-виртуализированной вилке. Лично я считаю, что ключевое слово new очень раздражает в Scala.


Есть только два решения, которые я вижу.

  • использовать макросы или плагин компилятора
  • использовать глобальный изменяемый объект строитель

Я знаю два проектов, которые могли бы сделать работу для вас с первым подходом:

Я пробовал с прежним. Я клонировал репозиторий и изменил scalaVersion в project/build.scala на "2.11.6" (вместо моментального снимка). Вы можете зайти в REPL, используя sbt sandbox/console.

Идея заключается в том, чтобы определить функции, которые принимают параметр отмечен @Implicit и таким образом вы можете «клей» внешние и внутренние части DSL дерева вместе:

import org.dslparadise.annotations._ 
import scala.collection.mutable.Builder 

case class Outer(inner: Inner) 
case class Inner(id: String, values: List[Value]) 
case class Value(value: String) 

def outer(i: Inner) = Outer(i) // nothing special here 

def inner(id: String) 
     (body: (Builder[Value, List[Value]] @Implicit) => Unit): Inner = { 
    val b = List.newBuilder[Value] // to "build" the contents of inner 
    body(b) 
    Inner(id, b.result) 
} 

def value(x: String)(implicit b: Builder[Value, List[Value]]): Value = { 
    val v = Value(x) 
    b += v // side-effect: populate the builder 
    v 
} 

Пример:

scala> outer { 
    | inner(id = "asdf") { 
    |  value("v1") 
    |  value("v2") 
    | } 
    | } 
res1: Outer = Outer(Inner(asdf,List(Value(v1), Value(v2)))) 

Voila!


Решение без плагина/макросов будет в настройке, например ThreadLocal строителей, но у вас нет времени компиляции безопасности:

val values = new ThreadLocal[Builder[Value, List[Value]]] 

def inner(id: String)(body: => Unit): Inner = { 
    val prev = values.get() 
    values.set(List.newBuilder[Value]) 
    body 
    val v = values.get().result 
    values.set(prev) 
    Inner(id, v) 
} 

def value(x: String): Value = { 
    val v = Value(x) 
    values.get() += v 
    v 
} 

Пример:

scala> inner(id = "asdf") { value("v1"); value("v2") } 
res1: Inner = Inner(asdf,List(Value(v1), Value(v2))) 
+0

Этот блестящий. Я не задавал этот вопрос, но я рад, что наткнулся на этот ответ. Я пытался решить проблему, но мне никогда не приходило в голову, как легко это может быть с «Builder» и «dsl-paradise»! Благодаря! –