2015-11-07 1 views
28

Je cherche un code succinct pour initialiser les classes simples de cas Scala de chaînes (par exemple une ligne de csv):Construire des classes simples de cas Scala de cordes, strictement sans passe-partout

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

Quel est l'objet Creator plus simple à faire ? J'apprendrais beaucoup sur Scala de voir une bonne solution à cela.

(Notez que les objets compagnon Person, Book et Country ne devrait pas être forcé d'exister. Ce serait passe-partout!)

Répondre

46

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

+2

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

+3

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. –

+0

Merci Nicolas. HList est une construction informe? – marios

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

J'aime vraiment le fait que c'est simple, sans dépendance de paquet. Bien sûr, ce sera lent, en raison de la réflexion --- mais je m'en fous pour les fins actuelles. (Je suppose que la réponse sans forme fait la magie au moment de la compilation, sans réfléchir ...?!) –

+2

@PerfectTiling Le problème principal de cette approche est qu'elle est fragile et qu'elle casse au moment de l'exécution - la il est peu probable que le coût de la performance soit significatif dans la plupart des cas. Et bien, il n'y a pas de réflexion d'exécution dans la solution Shapeless (bien qu'elle utilise la réflexion - voir ma réponse [ici] (http://stackoverflow.com/a/33580411/334519) pour une discussion). –