2017-08-11 4 views
5

私はケースクラスに約20のフィールドを含みます。これらのフィールドはすべてプリミティブ型です。スカラーオプションパーサーを使用して一般的なケースクラスフィールドを解析する方法は?

case class A(f1: String, f2: Int .....) 

これらのフィールドはすべてコマンドラインから解析する必要があります(残念ながら)。 私がすることができますが、私は本当に私が反射によって、フィールド名および提出タイプを取得することができ、この20倍

opt[String]("f1") required() valueName "<f1>" action { (x, c) => 
    c.copy(f1 = x) 
    } text "f1 is required" 
//...repeat 20 times 

を書きたくはありませんが、私は内、この呼び出しにこれらの情報をスタックする方法が分かりませんforループ

私はシェイプレスと接続することができますが、私はまだこれに慣れていません。これはシェイプレスで行うことができますか?

==

Scalaのオプションパーザ=> scopt

+0

"scala option parser"とは、[scopt](https://github.com/scopt/scopt)を意味すると思いますか? – thibr

+0

あなたがこれをやりたいなら、あなたは形を変えることができます。あなたは 'LabelledGeneric'のようなものを探しています。 – Alec

+0

@Alec、車輪を再実装するのは、短いコードを書くだけで十分です。 – zinking

答えて

3

私はちょうどあなたが型崩れなどの一切のライブラリを望んでいない気づきました。それがどんな慰めであれ、これは最終的にマクロを反映するスカラーを置き換えるライブラリなので、ホイールを再構築することなくあなたが得る純粋なスカラとほぼ同じです。

私はこれに役立つかもしれない何かがあると思います。それは一種の重い解決策ですが、私はそれがあなたが求めていることをすると思います。

静的な注釈を作成するために、これは素晴らしいスケーラメタ(http://www.scalameta.org)ライブラリを使用します。あなたのケースクラスに注釈を付けると、このインラインマクロはあなたのコマンドライン引数に対して適切なscoptパーサを生成します。

あなたのbuild.sbtは、scalametaライブラリと同様、マクロparadiseプラグインが必要になります。これらをプロジェクトに追加することができます。

addCompilerPlugin("org.scalameta" % "paradise" % paradise cross CrossVersion.full) 
libraryDependencies ++= Seq(
    "org.scalameta" %% "scalameta" % meta % Provided, 
) 

これらのdepsをビルドに追加したら、マクロ用に個別のプロジェクトを作成する必要があります。モジュール自体は、「マクロ」と命名されている場合は

完全SBTプロジェクトの定義は、クラスを作成し、ここに静的な注釈で、

lazy val macros = project 
    .in(file("macros")) 
    .settings(
    addCompilerPlugin("org.scalameta" % "paradise" % paradise cross CrossVersion.full), 
    libraryDependencies ++= Seq(
     "org.scalameta" %% "scalameta" % "1.8.0" % Provided, 
    ) 
    ) 

ようになります。

その後、メインモジュールはマクロモジュールに依存します。次に、これは、コンパイル時にscopt定義を含めるために、あなたのケースクラスを拡大していきますので...

@Opts 
case class Options(name: String, job: String, age: Int, netWorth: Double, job_title: String) 

のようなあなたのケースクラスに注釈を付けることができます。ここでは、生成されたクラスを上から見たものがあります。

case class Options(name: String, job: String, age: Int, netWorth: Double, job_title: String) { 
    import scopt._ 

    def options: OptionParser[Options] = new OptionParser[Options]("Options") { 
    opt[String]("name").required().action((x, c) => c.copy(name = x)).text("name is required.") 
    opt[String]("job").required().action((x, c) => c.copy(job = x)).text("job is required.") 
    opt[Int]("age").required().action((x, c) => c.copy(age = x)).text("age is required.") 
    opt[Double]("netWorth").required().action((x, c) => c.copy(netWorth = x)).text("netWorth is required.") 
    opt[String]("job_title").required().action((x, c) => c.copy(job_title = x)).text("job_title is required.") 
    } 
} 

これはあなたのボイラープレートのトンを保存する必要があり、私はこれの専門家ではないですので、インラインマクロのより多くの知識を持つ人のために、私はこれより良いを書くことができる方法を教えて気軽にしてください。

これに関する適切なチュートリアルとドキュメントはhttp://scalameta.org/tutorial/#Macroannotationsにあります。この方法についてご質問がある場合は、お答えいたします。

+0

ありがとう、私はscalelessを望んでいない理由は、scala 2.10上にあるので、コンパイラプラグインの要件です。それを置いて、私はいくつかの汎用的で複雑な関数宣言だけでそれを解決すべきだと考えました、なぜそれは複雑でなければなりませんか? – zinking

+0

私は理解しています。スカラ2.10を使っていると思いますか?あなたが形容詞でないなら、コンパイラプラグインが機能する必要はありません。これは特に難しい問題ではないと思います。この問題は、コンパイル時に新しいコードを生成する必要があります。 –

+0

実際にコード生成が必要であるとどう結論しましたか?私はこれが実行時に実現できると思った。 – zinking

2

ランタイムリフレクションのみで実装されたバージョンです。

libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value 

コード:

import scala.collection.mutable 
import scala.reflect.runtime.universe._ 

def genericParser[T: TypeTag](programName: String): OptionParser[T] = new OptionParser[T](programName) { 
    val StringTpe: Type = typeOf[String] 

    val fields: List[MethodSymbol] = typeOf[T].decls.sorted.collect { 
    case m: MethodSymbol if m.isCaseAccessor ⇒ m 
    } 

    val values = mutable.Map.empty[TermName, Any] 

    /** 
    * Returns an instance of a [[scopt.Read]] corresponding to the provided type 
    */ 
    def typeToRead(tpe: Type): Read[Any] = (tpe match { 
    case definitions.IntTpe ⇒ implicitly[Read[Int]] 
    case StringTpe   ⇒ implicitly[Read[String]] 
     // Add more types if necessary... 
    }) map identity[Any] 

    for (f ← fields) { 
    // kind of dynamic implicit resolution 
    implicit val read: Read[Any] = typeToRead(f.returnType) 
    opt[Any](f.name.toString) required() valueName s"<${f.name}>" foreach { value ⇒ 
     values(f.name) = value 
    } text s"${f.name} is required" 
    } 

    override def parse(args: Seq[String], init: T): Option[T] = { 
    super.parse(args, init) map { _ ⇒ 
     val classMirror = typeTag[T].mirror.reflectClass(typeOf[T].typeSymbol.asClass) 
     val constructor = typeOf[T].decl(termNames.CONSTRUCTOR).asMethod 
     val constructorMirror = classMirror.reflectConstructor(constructor) 
     val constructorArgs = constructor.paramLists.flatten.map(symbol ⇒ values(symbol.asTerm.name)) 

     constructorMirror(constructorArgs: _*).asInstanceOf[T] 
    } 
    } 
} 

使用例:

case class A(f1: String, f2: Int) 

println(genericParser[A]("main").parse(args, A("", -1))) 

いくつかのことに、それはマクロベースのソリューションよりも少ないエレガントですが、それだけでScalaの-reflect.jarが必要です考慮に入れて:

  • パラメータそれらが解析されるときに変更可能なMapに格納されます。クラスコンストラクタを使用して最終ステップで実行されるケースクラス変換(copyメソッドは含まれません)。
  • したがって、parseメソッドで渡された初期値はまったく使用されません(しかし、すべての引数が必要なので重要ではありません)。
  • あなたのニーズ(あなたのケースクラス値のタイプ)に応じて、さまざまなタイプの引数をサポートするために、コードを微調整する必要があります。私はStringIntを追加しました(必要ならさらにを追加してください...コメント)。
関連する問題