Je vais donner une solution qui est à peu près aussi simple que vous pouvez obtenir compte tenu des contraintes raisonnables sur la sécurité de type (sans exceptions d'exécution, pas de réflexion d'exécution, etc.), en utilisant Shapeless pour la dérivation générique:
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))
}
Ce travail exactement comme spécifié (bien qu'il faille noter que les valeurs sont enveloppés dans Option
pour représenter le fait que t il peut échouer l'opération analyse syntaxique):
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
est ici une classe de type qui fournit la preuve que nous pouvons analyser une chaîne dans un type donné. Nous devons fournir des instances explicites pour les types de base comme String
, Int
, etc., mais nous pouvons utiliser Shapeless pour générer des instances génériques pour les classes de cas (en supposant que nous ayons Creator
instances pour tous leurs types de membres).
C'est assez cool Travis! Pouvez-vous expliquer cette notation un peu 'Creator [H :: T]'? Comment est-ce que je lis le type ici? – marios
Ceci indique "HList dont la tête est de type H et dont la queue est de type T". T est soit un autre type contre (H2 :: T2) soit HNil pour indiquer qu'il n'y a pas d'autres valeurs dans cette liste. –
Merci Nicolas. HList est une construction informe? – marios