scala constructor boilerplate case-class

Construyendo clases de cajas Scala simples a partir de cuerdas, estrictamente sin placa de caldera



constructor boilerplate (2)

Busco un código sucinto para inicializar clases de casos de Scala simples desde cadenas (por ejemplo, una línea 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")

¿Cuál es el objeto Creator más simple para hacer esto? Aprendería mucho sobre Scala si viera una buena solución para esto.

(Tenga en cuenta que los objetos complementarios Person , Book y Country no deben ser obligados a existir. ¡Eso sería una placa de calderas!)


Daré una solución que es tan simple como se le pueden dar algunas restricciones razonables sobre la seguridad de los tipos (sin excepciones de tiempo de ejecución, sin reflejo en el tiempo de ejecución, etc.), usando Shapeless para la derivación genérica:

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

Esto funciona exactamente como se especifica (aunque tenga en cuenta que los valores están ajustados en Option para representar el hecho de que la operación de análisis puede fallar):

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 aquí es una clase de tipo que proporciona evidencia de que podemos analizar una cadena en un tipo dado. Tenemos que proporcionar instancias explícitas para tipos básicos como String , Int , etc., pero podemos usar Shapeless para derivar genéricamente instancias para clases de casos (asumiendo que tenemos instancias de Creator para todos sus tipos de miembros).


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] } }