Прежде всего, вывод метода convert
должен быть Option[Output]
или некоторой монадой Output
(Try
, Either
, scalaz.\/
, scalaz.Validation
и т. д.) на случай, если содержимое Seq[String]
не может быть преобразовано в Output
(неправильная длина Seq
, ошибки при разборе Int
s или 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