Scala DSL没有额外的语法

时间:2010-07-19 09:48:06

标签: scala hierarchy immutability dsl

我几次问自己这个问题并想出一个解决方案,感觉很脏。也许你可以给我任何建议,因为我认为这是用Scala写的每个DSL的基本问题。

我希望拥有嵌套对象的层次结构,而不添加任何额外的语法。 Specs就是一个很好的例子:

MySpec extends Specification {
  "system" should {
    "example0" in { ... }
    "example1" in { ... }
    "example2" in { ... }
  }

  "system" can {
    "example0" in { ... }
  }
}

例如,我不必写"example0" in { ... } :: "example1" in { ... } :: "example2" in { ... } :: Nil

这与我想要的完全相同。我认为这是通过规范类中的规范类中的隐式定义来实现的(如果您是规范作者并且我很难理解某些内容,请不要被冒犯:))

implicit def sus2spec(sus: Sus): Specification = {
  suslist += sus
  this
}

当我想要嵌套这样的对象时,我的主要问题就出现了。想象一下,我有这个语法:

root: statement*;

statement:
    IDENT '{' statement* '}'
  | declaration*
  ;

declaration: IDENT ':=' INT+;

我想把它翻译成一个看起来像这样的DSL:

MyRoot extends Root {
  "statement0" is {
    "nested_statement0" is {
      "nested_nested_statement0" is {
        "declaration0" := 0
      }

      "declaration1" := 1
      "declaration2" := 2
    }

    "declaration3" := 3
  }

  "statement1" is {
    "declaration4" := 4
  }
}

这里出现的问题对我来说隐式解决方案不起作用。隐式定义将在根对象的范围内执行,这意味着我将所有对象添加到根,并且层次结构将丢失。

然后我想我可以使用类似Stack [Statement]的东西。我可以在每次调用is时将对象推送到它,但感觉非常脏。

将问题放在一个句子中:如何在不添加任何额外语法的情况下创建一个与其层次结构相关的递归DSL,是否只有使用不可变对象的解决方案?

2 个答案:

答案 0 :(得分:2)

我在XScalaWT中看到了一个很好的技巧来实现DSL中的嵌套。我没有检查规格是否使用相同或不同的东西。

我认为以下示例显示了主要想法。它的核心是设置功能:它接受一些功能(更确切地说是闭合,如果我没有弄错的话),它只需要一个Nestable并且会在当前的那个上调用它们。
printName碰巧就是这样一个方法,就像addChild一样,为第一个params列表填充了参数。

对我来说,理解这是一个有启发性的部分。之后,您可以相对简单地添加许多其他奇特的功能(如隐式魔法,基于结构类型的dsl方法等)。

当然你可以拥有任何“类似上下文”的类而不是Nestable,特别是如果你选择纯不可变的东西。如果父母需要提及孩子,你可以在设置()期间收集孩子,并在最后创建父母。

在这种情况下,您可能会有类似

的内容
  private def setupChildren[A, B](a : A, setups:(A => B)*) : Seq[B] = {
    for (setup <- setups) yield setup(a)
  }

您将传入“上下文”,并使用返回的子项创建父项。

BTW我认为在XScalaWT中需要这个设置,因为它适用于SWT,其中子对象需要对其父控件的引用。如果您不需要它(或当前“上下文”中的任何内容),那么一切都变得容易一些 使用具有适当应用方法的伴随对象应该主要解决问题。他们很可能也应该接受其他功能,(如果你需要更多的话,可以使用相同数量的参数,或者一个元组)。

这个技巧的一个缺点是你必须为你想要在类上调用的每个方法都有一个单独的dsl方法(即使是一个简单的方法)。或者,您可以使用

之类的行
x => x.printName

这将完成这项工作,但不太好(特别是如果你必须经常这样做)。

object NestedDsl {

  object Nestable {
    def apply(name: String, setups:(Nestable => Unit)*): Nestable = {
      val n = new Nestable(None, name)
      setup(n, setups: _*)
      n
    }
  }

  class Nestable(parent: Option[Nestable], name: String) {
    def printName() { println(name) }
  }

  // DSL part

  def addChild(name: String, setups:(Nestable => Unit)*)(parent: Nestable) = {
    val n = new Nestable(Some(parent), name)
    setup(n, setups: _*)
    n
  }

  def printName(n: Nestable) = n.printName

  private def setup[T](t : T, setups:(T => Unit)*) : T = {
    setups.foreach(setup => setup(t))
    t
  }

  def main(args: Array[String]) {
    Nestable("root",
      addChild(
          "first", 
          addChild("second",
            printName
        )
      )
    )
  }
}

答案 1 :(得分:2)

我已经看过规格,他们没有做任何不同的事情。基本上你只需要一个可变堆栈。您可以在此处查看结果:cssx-dsl

代码非常简单。基本上我有mutable builder并且之后将其转换为不可变表示。