2015-11-07 45 views
28

我追求簡潔的代碼從字符串(如CSV行)初始化簡單的Scala case類:構建簡單的Scala case類的字符串,嚴格不鍋爐板

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不應該被強迫存在。這將是鍋爐板!)

回答

46

我要去給一個解決方案,是關於簡單,你可以得到鑑於有關類型安全(無運行時異常,沒有運行時反射等)的一些合理的限制,使用Shapeless對於通用推導:

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表示這項工作t的事實他解析操作可能會失敗):

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這裏是一個類型的類,它提供的證據表明,我們可以解析字符串轉換成指定的類型。我們必須提供像String,Int等基本類型的明確實例,但是我們可以使用Shapeless來一般地爲案例類推導實例(假設我們對所有成員類型都有Creator實例)。

+2

這是非常酷的特拉維斯!你能解釋一下這個符號嗎?創造者[H :: T]'?我如何閱讀這裏的類型? – marios

+3

這是「頭部類型爲H,尾部類型爲T的HList」。 T是另一種類型的級別缺陷(H2 :: T2)或HNil,表示該列表中沒有其他值。 –

+0

謝謝Nicolas。 HList是一個無形的構造? – 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

我真的很喜歡這個很簡單的事實,沒有軟件包依賴。當然,由於反思它會很慢 - 但我不在乎目前的目的。 (我假設基於無形的答案在編譯時做到了魔術,沒有反射......?) –

+2

@PerfectTiling這種方法的主要問題是它很脆弱,當它破壞時,會在運行時中斷 - 在大多數情況下,性能成本可能不會很大。而且,沒有Shapeless解決方案中的運行時反射(儘管它使用反射 - 請參閱我的答案[這裏](http://stackoverflow.com/a/33580411/334519)進行一些討論)。 –