9

当我尝试在 Scala 中构建内部 DSL 时,我遇到了一个常见问题并且我无法制定解决方案。为了让事情看起来更像一种典型的语言,我希望语法看起来像这样:

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

在实践中,有几个问题。第一个问题是让一个model对象以这种方式接受两个参数。如果有人有任何想法,请告诉我。但我所做的是做一些更像这样的事情:

model('Foo) {
  ...
}

其中 model 现在是一个函数,然后返回一个带有apply方法的对象,然后使用后面的 lambda。我可以忍受的。我也可以在 lambda 内部遇到类似的问题,所以内部有类似的decl 'Real 'x问题decl('Real,'x)。但我想要做的是获得波浪花括号内所有这些表达式的结果,以作为列表“返回”。换句话说,我想要的是这样写:

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

wheredecl(...)评估为某种类型Declaration{...}然后评估为List[Declaration]. 我怀疑有一些方法可以使用隐式来做到这一点,但我一直没能找到它。简而言之,我想做:

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

...评估为...的等价物

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

意见或建议?

4

3 回答 3

4

作为第一个想法,您可以尝试变量参数列表,它允许您使用逗号而不是分号:

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

或者,您可以扩展 atrait以去掉一些括号和逗号:

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" )
}
于 2012-04-13T10:28:18.933 回答
4

天啊,我都做了什么?

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))))
于 2012-04-14T00:22:27.517 回答
2

好的,在意识到'Foo应该是模型名称后彻底修改了这个。

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

然后在使用现场:

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

所以这里的噱头是

1)使用变量来跟踪当前模型

2) 使用符号作为方法名称(如果您喜欢括号-,可以使用)apply

3)使用构建器,以便返回的类可以是不可变的

虽然,TBH 这可能有点多,只是为了避免一些逗号...... :)

于 2012-04-13T15:16:13.673 回答