【问题标题】:How can I dynamically construct a Scala class without knowing types in advance?如何在事先不知道类型的情况下动态构造 Scala 类?
【发布时间】:2018-05-15 03:29:55
【问题描述】:

我想构建一个简单的库,开发人员可以在其中定义一个表示命令行参数的 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 可以有任何数量或类型,我不知道如何迭代命令行参数,将它们转换或转换为预期的类型,然后使用结果构造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)
}

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

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    当您想对case class(或Tuple)形状进行抽象时,标准方法是借助shapeless 库获取case classHList 表示。 HList 在其类型签名中跟踪其元素的类型及其数量。然后你可以在HList上递归实现你想要的算法。 Shapeless 还提供了许多有用的HLists 在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)
    }
    

    如果您需要支持除StringInt 之外的其他参数类型,您可以定义更多此类型类的实例。

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

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

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

    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)
    

    有一个很好的使用shapeless 解决类似问题的指南,您可能会觉得很有帮助:The Type Astronaut’s Guide to Shapeless

    【讨论】:

    • 耶哈!我
    猜你喜欢
    • 2010-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 2013-05-21
    • 2012-09-19
    • 1970-01-01
    相关资源
    最近更新 更多