Подтвердить что ты не робот

Построение простых классов классов Scala из строк, строго без котельной пластины

Я ищу сжатый код для инициализации простых классов классов Scala из строк (например, строки csv):

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, увидев хорошее решение для этого.

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

4b9b3361

Ответ 1

Я собираюсь дать решение, которое так же просто, как вы можете получить некоторые разумные ограничения безопасности типов (без исключений во время выполнения, без отражения во время выполнения и т.д.), используя 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 для представления факта, что операция синтаксического анализа может завершиться с ошибкой):

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

Ответ 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]
  }
}