Преобразование scala List[String]/List[Object] в модель/HList/tuple

Внешняя система возвращает Seq[String] (вид БД, вывод типа CSV/json), это обертка базовых типов: строка/числа. Я предпочитаю работать со своей собственной моделью.

object Converter {
  type Output = (Int, String, Double) // for instance 
  def convert(values: List[String]): Output
}

Очевидно, я не хочу каждый раз реализовывать convert-метод.

Похоже, мне нужно что-то попроще, чем http://nrinaudo.github.io/tabulate/tut/parsing.html

Можно ли здесь использовать HList? Подобно преобразованию размера HList (String::String::String::HNil) в модель за счет явного определения только типа вывода.


person Pavel Ajtkulov    schedule 24.01.2016    source источник


Ответы (1)


Прежде всего, вывод метода convert должен быть Option[Output] или некоторой монадой Output (Try, Either, scalaz.\/, scalaz.Validation и т. д.) на случай, если содержимое Seq[String] не может быть преобразовано в Output (неправильная длина Seq, ошибки при разборе Ints или Doubles и т.д.)

Возможная реализация с shapeless будет иметь класс типов для преобразования String в его тип параметра и вспомогательный класс типов для преобразования HList из String в представление HList для Output с первым классом типов.

Вот пример реализации:

import shapeless._
import shapeless.syntax.std.traversable._
import shapeless.ops.traversable._

trait Parse[Out] {
  def apply(value: String): Option[Out]
}
object Parse {
  implicit object convertToInt extends Parse[Int] {
    def apply(value: String) = Try(value.toInt).toOption
  }

  implicit object convertToString extends Parse[String] {
    def apply(value: String) = Some(value)
  }

  implicit object convertToDouble extends Parse[Double] {
    def apply(value: String) = Try(value.toDouble).toOption
  }
}

trait ParseAll[Out] {
  type In <: HList
  def apply(values: In): Option[Out]
}

object ParseAll {

  type Aux[I, O] = ParseAll[O] { type In = I }

  implicit object convertHNil extends ParseAll[HNil] {
    type In = HNil
    def apply(value: HNil) = Some(HNil)
  }

  implicit def convertHList[T, HO <: HList](implicit 
    cv: Parse[T], 
    cl: ParseAll[HO]
  ) = new ParseAll[T :: HO] {
    type In = String :: cl.In
    def apply(value: In) = value match {
      case x :: xs => for {
        t <- cv(x)
        h0 <- cl(xs)
      } yield t :: h0
    }
  }
}

trait Converter {
  type Output
  def convert[S <: HList, H <: HList](values: List[String])(implicit
    gen: Generic.Aux[Output, H], // Compute HList representation `H` of Output
    parse: ParseAll.Aux[S, H],   // Generate parser of Hlist of String `S` to HList `H`
    ft: FromTraversable[S]       // Generate converter of `List[String]` to HList of Strings `S`
  ): Option[Output] =
    values.toHList[S].flatMap(parse.apply).map(gen.from)
}

Эту реализацию легко обновить, чтобы она возвращала выбранную вами монаду ошибок (или генерировала исключения) вместо возврата Option.

И вот как вы можете его использовать:

scala> object ConverterISD extends Converter {
  type Output = (Int, String, Double)
}

defined object ConverterISD

scala> ConverterISD.convert(List("1", "foo", "2.34"))
res0: Option[ConverterISD.Output] = Some((1,foo,2.34))

scala> ConverterISD.convert(List("1", "foo", "2.34", "5"))
res1: Option[ConverterISD.Output] = None

scala> ConverterISD.convert(List("1", "foo", "bar"))
res2: Option[ConverterISD.Output] = None

Он также работает с классами case вместо кортежей:

scala> case class Model(i: Int, d: Double)

defined class Model

scala> object ConverterModel extends Converter {
  type Output = Model
}

defined object ConverterModel

scala> ConverterModel.convert(List("1", "2.34"))
res0: Option[ConverterModel.Output] = Some(Model(1,2.34))

scala> ConverterModel.convert(List("1"))
res1: Option[ConverterModel.Output] = None

scala> ConverterModel.convert(List("1", "foo")) 
res2: Option[ConverterModel.Output] = None
person Kolmar    schedule 25.01.2016