5

代数の定義を表すために、Scalaで内部DSLを構築しようとしています。この単純化されたデータモデルを考えてみましょう。

case class Var(name:String)
case class Eq(head:Var, body:Var*)
case class Definition(name:String, body:Eq*)

たとえば、簡単な定義は次のようになります。

val x = Var("x")
val y = Var("y")
val z = Var("z")
val eq1 = Eq(x, y, z)
val eq2 = Eq(y, x, z)
val defn = Definition("Dummy", eq1, eq2)

このような方程式を次の形式で表すための内部DSLが必要です。

Dummy {
   x = y z
   y = x z
}

私が得ることができる最も近いものは次のとおりです。

Definition("Dummy") := (
    "x" -> ("y", "z")
    "y" -> ("x", "z")
)

私が遭遇した最初の問題は、DefinitionとVarに対して2つの暗黙的な変換を行うことができないことDefinition("Dummy")です。ただし、主な問題はリストです。()などのように囲んだり、要素をコンマで区切ったりしたくありません。

Scalaを使用して私が望むことは可能ですか?はいの場合、誰かが私にそれを達成する簡単な方法を教えてもらえますか?

4

3 に答える 3

11

Scalas構文は強力ですが、シンボルの任意の区切り文字を作成するのに十分な柔軟性はありません。したがって、コンマを残してスペースのみに置き換える方法はありません。

それでも、コンパイル時にマクロを使用して、任意の内容の文字列を解析することは可能です。これは「簡単な」ソリューションではありませんが、機能するソリューションです。

object AlgDefDSL {

  import language.experimental.macros

  import scala.reflect.macros.Context

  implicit class DefDSL(sc: StringContext) {
    def dsl(): Definition = macro __dsl_impl
  }

  def __dsl_impl(c: Context)(): c.Expr[Definition] = {
    import c.universe._

    val defn = c.prefix.tree match {
      case Apply(_, List(Apply(_, List(Literal(Constant(s: String)))))) =>

        def toAST[A : TypeTag](xs: Tree*): Tree =
          Apply(
            Select(Ident(typeOf[A].typeSymbol.companionSymbol), newTermName("apply")),
            xs.toList
          )

        def toVarAST(varObj: Var) =
          toAST[Var](c.literal(varObj.name).tree)

        def toEqAST(eqObj: Eq) =
          toAST[Eq]((eqObj.head +: eqObj.body).map(toVarAST(_)): _*)

        def toDefAST(defObj: Definition) =
          toAST[Definition](c.literal(defObj.name).tree +: defObj.body.map(toEqAST(_)): _*)

        parsers.parse(s) match {
          case parsers.Success(defn, _)  => toDefAST(defn)
          case parsers.NoSuccess(msg, _) => c.abort(c.enclosingPosition, msg)
        }
    }
    c.Expr(defn)
  }

  import scala.util.parsing.combinator.JavaTokenParsers

  private object parsers extends JavaTokenParsers {

    override val whiteSpace = "[ \t]*".r

    lazy val newlines =
      opt(rep("\n"))

    lazy val varP =
      "[a-z]+".r ^^ Var

    lazy val eqP =
      (varP <~ "=") ~ rep(varP) ^^ {
        case lhs ~ rhs => Eq(lhs, rhs: _*)
      }

    lazy val defHead =
      newlines ~> ("[a-zA-Z]+".r <~ "{") <~ newlines

    lazy val defBody =
      rep(eqP <~ rep("\n"))

    lazy val defEnd =
      "}" ~ newlines

    lazy val defP =
      defHead ~ defBody <~ defEnd ^^ {
        case name ~ eqs => Definition(name, eqs: _*)
      }

    def parse(s: String) = parseAll(defP, s)
  }

  case class Var(name: String)
  case class Eq(head: Var, body: Var*)
  case class Definition(name: String, body: Eq*)
}

次のようなもので使用できます。

scala> import AlgDefDSL._
import AlgDefDSL._

scala> dsl"""
     | Dummy {
     |   x = y z
     |   y = x z
     | }
     | """
res12: AlgDefDSL.Definition = Definition(Dummy,WrappedArray(Eq(Var(x),WrappedArray(Var(y), Var(z))), Eq(Var(y),WrappedArray(Var(x), Var(z)))))
于 2013-01-26T14:56:34.233 に答える
5

sschaef の優れたソリューションに加えて、DSL のリスト構築でコンマを取り除くために一般的に使用されるいくつかの可能性について言及したいと思います。

コロン

これは些細なことかもしれませんが、解決策として見落とされることがあります。

line1 ::
line2 ::
line3 ::
Nil

DSL では、いくつかの命令/データを含むすべての行が同じ方法で終了することがしばしば望まれます (最終行以外のすべてがカンマを取得するリストとは対照的です)。このようなソリューションを使用すると、行を交換しても、末尾のコンマが台無しになることはありません。残念ながら、Nil見た目が少し悪いです。

流体 API

DSL にとって興味深いかもしれない別の代替案は、次のようなものです。

BuildDefinition()
.line1
.line2
.line3
.build

ここで、各行はビルダーのメンバー関数です (そして、変更されたビルダーを返します)。このソリューションでは、最終的にビルダーをリストに変換する必要があります (暗黙の変換として行われる場合があります)。一部の API では、ビルダー インスタンス自体を渡し、必要な場所でのみデータを抽出できる場合があることに注意してください。

コンストラクター API

同様に、別の可能性はコンストラクターを利用することです。

new BuildInterface {
  line1
  line2
  line3
}

ここでBuildInterfaceはトレイトであり、インターフェースから無名クラスをインスタンス化するだけです。行関数は、このトレイトのいくつかのメンバー関数を呼び出します。呼び出しごとに、ビルド インターフェイスの状態を内部的に更新できます。これは通常、変更可能なデザインになることに注意してください(ただし、構築中のみ)。リストを抽出するには、暗黙的な変換を使用できます。

私はあなたの DSL の実際の目的を理解していないので、これらの手法のいずれかがあなたのシナリオにとって興味深いものであるかどうかはよくわかりません。「、」を取り除く一般的な方法であるため、追加したかっただけです。

于 2013-01-28T09:28:45.277 に答える