scala - 如何使用 scala 选项解析器解析通用案例类字段?

标签 scala generics shapeless

我有一个案例类,包括大约 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 循环中的此调用中

我可以将它与 shapeless 联系起来,但我仍然不熟悉,并且可以在没有 shapeless 的情况下完成吗?

==

scala 选项解析器 => scopt

最佳答案

这是一个仅使用运行时反射实现的版本。虽然它不如基于宏的解决方案优雅,但它只需要 scala-reflect.jar:

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

需要考虑的几点:
  • 参数在解析时存储在可变 Map 中。案例类转换在最后一步使用类构造函数执行(不涉及 copy 方法)。
  • 因此,在 parse 中传递的初始值根本不使用方法(但这无关紧要,因为所有参数都是必需的)。
  • 您必须根据需要(案例类值的类型)调整代码以支持不同类型的参数。我只添加了StringInt (如有必要,请参阅添加更多类型...评论)。
  • 关于scala - 如何使用 scala 选项解析器解析通用案例类字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45630693/

    相关文章:

    scala - 如何使用无形来迭代联产品中的所有产品类型?

    scala - 使用 Scala 和 logback 配置 Apache Spark 日志记录

    带有 scala setAdapter InvocationTargetException 的 Android

    c++ - constexpr模板函数的无限递归

    c# - 我如何从 Func<Object> 转换为 Func<dynamicType> c#

    scala - Shapeless:检查多态函数的类型约束

    scala - 在无形状状态下动态参数化 Poly1 函数

    java - 为什么 Scala 在读取我的 CSV 时会崩溃?

    javascript - Scala + Web 框架 + Javascript 框架有哪些好的组合?

    java - 非静态通用函数,调用作为参数传入的方法