如何在不预先知道类型的情况下动态构造Scala类?

如何在不预先知道类型的情况下动态构造Scala类?,scala,shapeless,Scala,Shapeless,我想构建一个简单的库,开发人员可以在其中定义一个表示命令行参数的Scala类(为了保持简单,只需要一组必需的参数——没有标志或可选参数)。我希望库解析命令行参数并返回该类的实例。库的用户只需执行以下操作: case class FooArgs(fluxType: String, capacitorCount: Int) def main(args: Array[String]) { val argsObject: FooArgs = ArgParser.parse(args).as[Foo

我想构建一个简单的库,开发人员可以在其中定义一个表示命令行参数的Scala类(为了保持简单,只需要一组必需的参数——没有标志或可选参数)。我希望库解析命令行参数并返回该类的实例。库的用户只需执行以下操作:

case class FooArgs(fluxType: String, capacitorCount: Int)

def main(args: Array[String]) {
  val argsObject: FooArgs = ArgParser.parse(args).as[FooArgs]
  // do real stuff 
}
如果提供的参数与预期的类型不匹配(例如,如果有人在预期的
Int
位置传递字符串“bar”),则解析器应该抛出运行时错误

如何在不事先知道其形状的情况下动态构建
FooArgs
?由于
FooArgs
可以有任何arity或类型,我不知道如何迭代命令行参数,将它们强制转换或转换为预期的类型,然后使用结果构造
FooArgs
。基本上,我想做以下几点:

// ** notional code - does not compile **
def parse[T](args: Seq[String], klass: Class[T]): T = {
  val expectedTypes = klass.getDeclaredFields.map(_.getGenericType)
  val typedArgs = args.zip(expectedTypes).map({
      case (arg, String)      => arg
      case (arg, Int)         => arg.toInt
      case (arg, unknownType) => 
        throw new RuntimeException(s"Unsupported type $unknownType")
  })
  (klass.getConstructor(typedArgs).newInstance _).tupled(typedArgs)
}

关于如何实现这样的目标,有什么建议吗?

当您想抽象
案例类
(或
元组
)形状时,标准方法是借助
无形状
库获得
案例类
列表
表示
HList
在其类型签名中跟踪其元素的类型及其数量。然后可以在
HList
上递归地实现所需的算法。Shapless还提供了
shapless.ops.HList
中的
HList
s的许多有用转换

对于这个问题,首先我们需要定义一个辅助类型类来解析
String
中某种类型的参数:

trait Read[T] {
  def apply(str: String): T
}

object Read {
  def make[T](f: String => T): Read[T] = new Read[T] {
    def apply(str: String) = f(str)
  }

  implicit val string: Read[String] = make(identity)

  implicit val int: Read[Int] = make(_.toInt)
}
如果需要支持除
String
Int
以外的其他参数类型,则可以定义此typeclass的更多实例

然后我们可以定义将一系列参数解析为某种类型的实际类型类:

// This is needed, because there seems to be a conflict between
// HList's :: and the standard Scala's ::
import shapeless.{:: => :::, _}

trait ParseArgs[T] {
  def apply(args: List[String]): T
}

object ParseArgs {
  // Base of the recursion on HList
  implicit val hnil: ParseArgs[HNil] = new ParseArgs[HNil] {
    def apply(args: List[String]) =
      if (args.nonEmpty) sys.error("too many args")
      else HNil
  }

  // A single recursion step on HList
  implicit def hlist[T, H <: HList](
    implicit read: Read[T], parseRest: ParseArgs[H]
  ): ParseArgs[T ::: H] = new ParseArgs[T ::: H] {
    def apply(args: List[String]) = args match {
      case first :: rest => read(first) :: parseRest(rest)
      case Nil => sys.error("too few args")
    }
  }

  // The implementation for any case class, based on its HList representation
  implicit def caseClass[C, H <: HList](
    implicit gen: Generic.Aux[C, H], parse: ParseArgs[H]
  ): ParseArgs[C] = new ParseArgs[C] {
    def apply(args: List[String]) = gen.from(parse(args))

  }
}
还有一个简单的测试:

scala> ArgParser.parse(Array("flux", "10")).to[FooArgs]
res0: FooArgs = FooArgs(flux,10)
关于使用
shapeless
解决类似问题,有一个很好的指南,您可能会发现它很有用:

Yeeha!我
scala> ArgParser.parse(Array("flux", "10")).to[FooArgs]
res0: FooArgs = FooArgs(flux,10)