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

时间:2018-05-07 16:20:52

标签: scala shapeless

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

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

def main(args: Array[String]) {
  val argsObject: FooArgs = ArgParser.parse(args).as[FooArgs]
  // do real stuff 
}

如果提供的参数与预期的类型不匹配,解析器应该抛出运行时错误(例如,如果有人传递字符串" bar"在预期Int的位置。)

如何在不事先了解其形状的情况下动态构建FooArgs?由于FooArgs可以有任何arity或类型,我不知道如何迭代命令行参数,转换或转换为期望的类型,然后使用结果构造{{1} }。基本上,我想沿着这些方向做点什么:

FooArgs

关于如何实现这样的目标的任何建议?

1 个答案:

答案 0 :(得分:2)

如果要抽象case class(或Tuple)形状,标准方法是在{{HList的帮助下获取case class的{​​{1}}表示形式1}}库。 shapeless会跟踪其元素类型及其数量的类型签名。然后,您可以在HList上递归地实现您想要的算法。 Shapeless还在HList中提供了HList s的一些有用的转换。

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

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

最后我们可以定义一些使用此类型类的API。例如:

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

  }
}

一个简单的测试:

case class ArgParser(args: List[String]) {
  def to[C](implicit parseArgs: ParseArgs[C]): C = parseArgs(args)
}

object ArgParser {
  def parse(args: Array[String]): ArgParser = ArgParser(args.toList)
}

使用scala> ArgParser.parse(Array("flux", "10")).to[FooArgs] res0: FooArgs = FooArgs(flux,10) 解决类似问题有一个很好的指南,您可能会觉得有用:The Type Astronaut’s Guide to Shapeless