Scala - 用最少的语法设计 DSL
Scala - designing DSL with mininal syntax
我希望在 Scala 中设计一个 DSL,尽可能减少语法错误。它旨在供不了解 Scala 的用户使用,但可以利用 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
(在这种情况下,以下代码块将被视为构造函数,我将能够调用成员函数)?
如您所见,归结为
is there a way to treat outer
as new Outer
不幸的是,答案是否定的。我认为这在实验性 Scala-Virtualized 分支中是可能的。就个人而言,我认为 new
关键字在 Scala 中也很烦人。
我看到的解决方案只有两个。
- 使用宏或编译器插件
- 使用全局可变构建器对象
我知道有两个项目可以使用第一种方法为您完成这项工作:
我试过前者。我克隆了存储库并将 project/build.scala
中的 scalaVersion
更改为 "2.11.6"
(而不是快照)。您可以使用 sbt sandbox/console
.
进入 REPL
我们的想法是定义带有标记为 @Implicit
的参数的函数,这样您就可以 "glue" 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))))
瞧!
没有 plugin/macros 的解决方案是设置例如 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)))
我希望在 Scala 中设计一个 DSL,尽可能减少语法错误。它旨在供不了解 Scala 的用户使用,但可以利用 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
(在这种情况下,以下代码块将被视为构造函数,我将能够调用成员函数)?
如您所见,归结为
is there a way to treat
outer
asnew Outer
不幸的是,答案是否定的。我认为这在实验性 Scala-Virtualized 分支中是可能的。就个人而言,我认为 new
关键字在 Scala 中也很烦人。
我看到的解决方案只有两个。
- 使用宏或编译器插件
- 使用全局可变构建器对象
我知道有两个项目可以使用第一种方法为您完成这项工作:
我试过前者。我克隆了存储库并将 project/build.scala
中的 scalaVersion
更改为 "2.11.6"
(而不是快照)。您可以使用 sbt sandbox/console
.
我们的想法是定义带有标记为 @Implicit
的参数的函数,这样您就可以 "glue" 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))))
瞧!
没有 plugin/macros 的解决方案是设置例如 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)))