从Strings构造简单的Scala案例类,严格没有样板

时间:2015-11-07 17:10:19

标签: scala constructor boilerplate case-class

我寻求简洁的代码来初始化Strings中的简单Scala案例类(例如csv行):

case class Person(name: String, age: Double)
case class Book(title: String, author: String, year: Int)
case class Country(name: String, population: Int, area: Double)

val amy = Creator.create[Person]("Amy,54.2")
val fred = Creator.create[Person]("Fred,23")
val hamlet = Creator.create[Book]("Hamlet,Shakespeare,1600")
val finland = Creator.create[Country]("Finland,4500000,338424")

最简单的Creator对象是什么?我会从Scala那里学到很多很好的解决方案。

(请注意,配对对象PersonBookCountry不应该被强制存在。那将是锅炉板!)

2 个答案:

答案 0 :(得分:46)

我将使用{{3}给出一个解决方案,只要你能得到一些关于类型安全的合理约束(没有运行时异常,没有运行时反射等)就可以得到解决方案。用于泛型推导:

import scala.util.Try
import shapeless._

trait Creator[A] { def apply(s: String): Option[A] }

object Creator {
  def create[A](s: String)(implicit c: Creator[A]): Option[A] = c(s)

  def instance[A](parse: String => Option[A]): Creator[A] = new Creator[A] {
    def apply(s: String): Option[A] = parse(s)
  }

  implicit val stringCreate: Creator[String] = instance(Some(_))
  implicit val intCreate: Creator[Int] = instance(s => Try(s.toInt).toOption)
  implicit val doubleCreate: Creator[Double] =
    instance(s => Try(s.toDouble).toOption)

  implicit val hnilCreator: Creator[HNil] =
    instance(s => if (s.isEmpty) Some(HNil) else None)

  private[this] val NextCell = "^([^,]+)(?:,(.+))?$".r

  implicit def hconsCreate[H: Creator, T <: HList: Creator]: Creator[H :: T] =
    instance {
      case NextCell(cell, rest) => for {
        h <- create[H](cell)
        t <- create[T](Option(rest).getOrElse(""))
      } yield h :: t
      case _ => None
    }

  implicit def caseClassCreate[C, R <: HList](implicit
    gen: Generic.Aux[C, R],
    rc: Creator[R]
  ): Creator[C] = instance(s => rc(s).map(gen.from))
}

这项工作完全符合指定(尽管请注意,这些值包含在Option中,以表示解析操作可能失败的事实):

scala> case class Person(name: String, age: Double)
defined class Person

scala> case class Book(title: String, author: String, year: Int)
defined class Book

scala> case class Country(name: String, population: Int, area: Double)
defined class Country

scala> val amy = Creator.create[Person]("Amy,54.2")
amy: Option[Person] = Some(Person(Amy,54.2))

scala> val fred = Creator.create[Person]("Fred,23")
fred: Option[Person] = Some(Person(Fred,23.0))

scala> val hamlet = Creator.create[Book]("Hamlet,Shakespeare,1600")
hamlet: Option[Book] = Some(Book(Hamlet,Shakespeare,1600))

scala> val finland = Creator.create[Country]("Finland,4500000,338424")
finland: Option[Country] = Some(Country(Finland,4500000,338424.0))

Creator这是一个类型类,它提供了我们可以将字符串解析为给定类型的证据。我们必须为StringInt等基本类型提供显式实例,但是我们可以使用Shapeless一般地为案例类派生实例(假设我们有Creator个实例他们的成员类型)。

答案 1 :(得分:2)

object Creator {
  def create[T: ClassTag](params: String): T = {
    val ctor = implicitly[ClassTag[T]].runtimeClass.getConstructors.head
    val types = ctor.getParameterTypes

    val paramsArray = params.split(",").map(_.trim)

    val paramsWithTypes = paramsArray zip types

    val parameters = paramsWithTypes.map {
      case (param, clas) =>
        clas.getName match {
          case "int" => param.toInt.asInstanceOf[Object] // needed only for AnyVal types
          case "double" => param.toDouble.asInstanceOf[Object] // needed only for AnyVal types
          case _ =>
            val paramConstructor = clas.getConstructor(param.getClass)
            paramConstructor.newInstance(param).asInstanceOf[Object]
        }

    }

    val r = ctor.newInstance(parameters: _*)
    r.asInstanceOf[T]
  }
}