2015-11-07 1 views
28

Я ищу лаконичный код для инициализации простых классов регистра Scala из строк (например, CSV линии):Построение простых классов регистра Scala из строк, строго без шаблонного

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 объект, чтобы сделать это ? Я много узнал о том, как Скала видит хорошее решение этого.

(Обратите внимание, что компаньон объекты Person, Book и Country не должны быть вынуждены существовать. Это было бы шаблонный!)

ответ

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 для генерации экземпляров для классов case (при условии, что у нас есть экземпляров для всех их типов членов).

+2

Это довольно крутой Тревис! Можете ли вы объяснить эту нотацию бит 'Creator [H :: T]'? Как я могу прочитать этот тип здесь? – marios

+3

Это читает «HList, чья голова имеет тип H и чей хвост имеет тип T». T является либо другим типом уровня cons (H2 :: T2), либо HNil, чтобы обозначить, что в этом списке нет дополнительных значений. –

+0

Спасибо, Николас. 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) для некоторого обсуждения). –

Смежные вопросы