2015-11-07 34 views
28

Ben Strings (örneğin bir csv hattı) basit Scala vaka sınıfları başlatmak için özlü kodunu aramak: Bunu yapmanın en kolay Creator nesne nedirkesinlikle kazan-plaka olmadan Strings basit Scala vaka sınıfları Oluşturma

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

? Scala'yı bunun için iyi bir çözüm görmekten çok şey öğrenirdim.

(yani refakatçi varlığını zorunda olmak. Yani kazan-plaka olacağını olmamalıdır Person, Book ve Country nesneleri Not!) Sana almak gibi yaklaşık olarak basit bir çözüm vereceğim

cevap

46

jenerik derivasyon için Shapeless kullanarak, tip güvenliği (hiçbir çalışma zamanı istisnaları, hiçbir çalışma zamanı yansıma, vb) hakkında bazı makul kısıtlamalar verilen:

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

değerleri Option sarılı olduğu not temsil etmek her ne kadar (belirtilen tam olarak bu çalışma t aslında o ayrıştırma işlemi başarısız olabilir): Burada

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 Verilen bir türü içine bir dize ayrıştırmak olabilir kanıtlar sağlayan bir tür sınıftır. String, Int, vb. Gibi temel türler için açık örnekler vermeliyiz, ancak vaka sınıfları için örneklerin türetilmesi için Shapeless'i kullanabiliriz (tüm üye türleri için Creator örneğimiz olduğunu varsayarak).

+2

Bu çok güzel Travis! Bu gösterimi biraz "Yaratıcı" [H :: T] 'yi açıklayabilir misiniz? Buradaki yazıyı nasıl okurum? – marios

+3

Bu, "kafası H tipi ve ucu T tipi" olan HList'i okur. T, bu listede başka değerler olmadığını belirtmek için başka bir tür seviye eksileri (H2 :: T2) veya HNil'dir. –

+0

Teşekkürler Nicolas. HList şekilsiz bir yapı mıdır? – 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

Gerçekten bu, paket bağımlılıkları olmadan, basit olduğu gerçeğini seviyorum. Tabii ki, yansıma nedeniyle yavaş olacak --- ama şu anki amaçlar için umurumda değil. (Shapeless tabanlı cevabın sihri, derleme zamanında, yansıma olmadan yaptığını farzediyorum ...?) –

+2

@PerfectTiling Bu yaklaşımla ilgili temel sorun kırılgan ve kırıldığında, çalışma zamanında kırılıyor. Çoğu durumda performans maliyetinin önemli olma ihtimali yoktur. Ve doğru, Shapeless çözümünde çalışma zamanı yansıması yok (yansıma kullanmasa da - bazı tartışmalar için cevabım [burada] (http://stackoverflow.com/a/33580411/334519) bakın). –