从Scala中的一系列表达式构造List

时间:2012-04-13 10:15:32

标签: scala dsl

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

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

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

model('Foo) {
  ...
}

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

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

其中decl(...)评估为Declaration类型的内容,而{...}则评估为List[Declaration]。我怀疑有一些方法可以使用implicits来做到这一点,但我一直无法找到它。简而言之,我想:

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

...评估相当于......

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

意见或建议?

3 个答案:

答案 0 :(得分: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" ) 
)

或者,您可以扩展trait以删除一些括号和逗号:

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

答案 1 :(得分: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))))

答案 2 :(得分: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)
  }
}

然后在use-site:

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这可能只是为了避免一些逗号......:)