by 壮衣
在上一篇博客《Scala类型类的小应用之CSV Encoder》中提供了一个类型类Encoder用于将指定类型A转换成CSV格式的String,这篇博文将介绍类型类Encoder的逆过程:提供类型类Decoder,将CSV格式的String转换成指定类型A或者异常Exception。
首先定义类型类:
trait Decoder[A] {
def apply(s: String): Either[Exception, A]
}
然后添加基本类型的类型类实例:
object Decoder {
def apply[A: Decoder](s: String): Either[Exception, A] = implicitly[Decoder[A]].apply(s)
implicit val shortDecoder: Decoder[Short] = new Decoder[Short] {
override def apply(s: String): Either[Exception, Short] =
try Right(s.toShort)
catch {
case e: Exception => Left(e)
}
}
implicit val intDecoder: Decoder[Int] = new Decoder[Int] {
override def apply(s: String): Either[Exception, Int] =
try Right(s.toInt)
catch {
case e: Exception => Left(e)
}
}
}
可以看到在添加类型类实例的时候有相同模式的代码,我们用一个高阶方法convert替换换模式代码:
def convert[A](s: String)(f: String => A): Either[Exception, A] =
try Right(f(s))
catch {
case e: Exception => Left(e)
}
现在我们利用convert方法来实现基本类型的类型类实例了:
object Decoder {
def apply[A: Decoder](s: String): Either[Exception, A] = implicitly[Decoder[A]].apply(s)
implicit val shortDecoder: Decoder[Short] = new Decoder[Short] {
override def apply(s: String): Either[Exception, Short] = convert(s)(_.toShort)
}
implicit val intDecoder: Decoder[Int] = new Decoder[Int] {
override def apply(s: String): Either[Exception, Int] = convert(s)(_.toInt)
}
implicit val longDecode: Decoder[Long] = new Decoder[Long] {
override def apply(s: String): Either[Exception, Long] = convert(s)(_.toLong)
}
implicit val floatDecode: Decoder[Float] = new Decoder[Float] {
override def apply(s: String): Either[Exception, Float] = convert(s)(_.toFloat)
}
implicit val doubleDecode: Decoder[Double] = new Decoder[Double] {
override def apply(s: String): Either[Exception, Double] = convert(s)(_.toDouble)
}
implicit val stringDecoder: Decoder[String] = new Decoder[String] {
override def apply(s: String): Either[Exception, String] = convert(s)(_.toString)
}
implicit val booleanDecoder: Decoder[Boolean] = new Decoder[Boolean] {
override def apply(s: String): Either[Exception, Boolean] = convert(s)(_.toBoolean)
}
}
先来测试下这些基本类型的类型类:
scala> import org.forcestudy.csvz.Decoder
import org.forcestudy.csvz.Decoder
scala> import org.forcestudy.csvz.Decoder._
import org.forcestudy.csvz.Decoder._
scala> Decoder[Int]("1")
res0: Either[Exception,Int] = Right(1)
scala> Decoder[Int]("zdx")
res1: Either[Exception,Int] = Left(java.lang.NumberFormatException: For input string: "zdx")
scala> Decoder[Boolean]("true")
res2: Either[Exception,Boolean] = Right(true)
可见当基本类型可以完成转换的时候返回Right[A],当转换出现异常的时候返回Left[Exception]。现在看下如何转换"1,2,3"到List(1, 2, 3),显然我们需要提供Decoder[List[A <: AnyVal]]的类型类实例,代码如下:
implicit def listValDecoder[A <: AnyVal](implicit ea: Decoder[A]): Decoder[List[A]] = new Decoder[List[A]] {
override def apply(s: String): Either[Exception, List[A]] = sequence(s.split(",").toList.map(ea.apply))
}
其中有一个方法sequence将List[Either[E, A]]转换成Either[E, List[A]],来看下代码:
def sequence[E, A](li: List[Either[E, A]]): Either[E, List[A]] = {
def loop(n: Int, res: Either[E, List[A]]): Either[E, List[A]] =
if (n < 0) res
else li(n) match {
case Left(e) => Left(e)
case Right(a) => loop(n - 1, res.map(a :: _))
}
loop(li.length - 1, Right(Nil))
}
现在可以测试“1,2,3”转换成List(1, 2, 3)了:
scala> Decoder[List[Int]]("1,2,3")
res4: Either[Exception,List[Int]] = Right(List(1, 2, 3))
scala> Decoder[List[Boolean]]("true,false,true")
res5: Either[Exception,List[Boolean]] = Right(List(true, false, true))
那我们想将字符串转换成元组又该如何处理呢?如将"zdx,29,145.0"转换成("zdx", 29, 145.0),当然可以添加对应类型的类型类实例,不过还是和上篇一样我们使用shapeless的Automatic type class instance derivation技术,代码如下:
object Decoder extends ProductTypeClassCompanion[Decoder] {
//code
override val typeClass: ProductTypeClass[Decoder] = new ProductTypeClass[Decoder] {
override def project[F, G](instance: => Decoder[G], to: (F) => G, from: (G) => F): Decoder[F] = new Decoder[F] {
override def apply(s: String): Either[Exception, F] = instance.apply(s).map(g => from(g))
}
override def emptyProduct: Decoder[HNil] = new Decoder[HNil] {
override def apply(s: String): Either[Exception, HNil] = Right(HNil)
}
override def product[H, T <: HList](ch: Decoder[H], ct: Decoder[T]): Decoder[::[H, T]] = new Decoder[::[H, T]] {
override def apply(s: String): Either[Exception, ::[H, T]] = s.split(",").toList match {
case cell +: rest => for {
head <- ch.apply(cell)
tail <- ct.apply(rest.mkString(","))
} yield head :: tail
}
}
}
}
其中我们对Either[E, A]类型调用了map方法并施用for表达式语法糖,这些都是原生Either类型不具备的方法,我们通过隐式类对Either类型进行扩张,代码如下:
implicit class EitherOps[E, A](ei: Either[E, A]) {
def map[B](f: A => B): Either[E, B] = ei match {
case Right(a) => Right(f(a))
case Left(e) => Left(e)
}
def flatMap[B](f: A => Either[E, B]): Either[E, B] = ei match {
case Right(a) => f(a)
case Left(e) => Left(e)
}
}
现在可以测试字符串转元组了:
scala> Decoder[(String, Int, Double)]("zdx,29,145.0")
res7: Either[Exception,(String, Int, Double)] = Right((zdx,29,145.0))
再来测试下字符串转样例类:
scala> case class Person(name: String, age: Int, Height: Double)
defined class Person
scala> Decoder[Person]("zdx,29,145.0")
res8: Either[Exception,Person] = Right(Person(zdx,29,145.0))
对于String转换成List[Person]该如何处理? 我们添加了List[A <: AnyVal]的类型类实例,但是Person是AnyRel的子类,那我们就来添加List[A <: AnyRef]的类型类实例:
implicit def listDefDecoder[A <: AnyRef](implicit ea: Decoder[A]): Decoder[List[A]] = new Decoder[List[A]] {
override def apply(s: String): Either[Exception, List[A]] = sequence(s.split("\n").toList.map(ea.apply))
}
现在测试下String转换成List[Person]:
scala> Decoder[List[Person]]("zdx,29,145.0\nygy,28,185")
res9: Either[Exception,List[Person]] = Right(List(Person(zdx,29,145.0), Person(ygy,28,185.0)))
再测试下String转换List[(String, Int, Double)]
scala> Decoder[List[(String, Int, Double)]]("zdx,29,145.0\nygy,28,185")
res10: Either[Exception,List[(String, Int, Double)]] = Right(List((zdx,29,145.0), (ygy,28,185.0)))
好了还差一步,我们通过隐式类给String类型添加as方法完成String类型转换成指定类型:
implicit class StringOps(s: String) {
def as[A: Decoder]: Either[Exception, A] = implicitly[Decoder[A]].apply(s)
}
现在我们来测试下as方法:
scala> "zdx,29,145.0".as[(String, Int, Double)]
res0: Either[Exception,(String, Int, Double)] = Right((zdx,29,145.0))
scala> "zdx,29,145.0\nygy,28,185.0".as[List[(String, Int, Double)]]
res3: Either[Exception,List[(String, Int, Double)]] = Right(List((zdx,29,145.0), (ygy,28,185.0)))
scala> case class Person(name: String, age: Int, height: Double)
defined class Person
scala> "zdx,29,145.0\nygy,28,185.0".as[List[Person]]
res4: Either[Exception,List[Person]] = Right(List(Person(zdx,29,145.0), Person(ygy,28,185.0)))
到此CSV的Decoder类型类就介绍完了,当然完成一个CSV的库还需要很多细节去做,这两篇博文只是提供了一个核心方案。此外关于shapeless的Automatic type class instance derivation技术我还会继续研究,请关注后续的博文。











网友评论