美文网首页
Scala类型类的小应用之CSV Decoder

Scala类型类的小应用之CSV Decoder

作者: 吐思圈 | 来源:发表于2018-01-12 22:51 被阅读0次

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),当然可以添加对应类型的类型类实例,不过还是和上篇一样我们使用shapelessAutomatic 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技术我还会继续研究,请关注后续的博文。

相关文章

  • Scala类型类的小应用之CSV Decoder

    by 壮衣 在上一篇博客《Scala类型类的小应用之CSV Encoder》中提供了一个类型类Encoder用于将...

  • Scala类型类的小应用之Functor Foldable

    by 壮衣 在之前的博文《Scala类型类的小应用之CSV Encoder》中有一段代码: 这两个方法分别提供了类...

  • Scala类型类的小应用之CSV Encoder

    by 壮衣 在上一篇博客《Scala由类的动态扩展想到类型类》中提到了类型类,写了一个类型类:Hello[A]并实...

  • Scala类型类的小应用之Cats

    by 壮衣 在上一篇博文《Scala类型类的小应用之Functor Foldable》中留了一个尾巴:介绍Func...

  • [译]Scala统一的类型

    在Scala中,所有值都有类型,包括数值和函数。下图说明了类的层次结构。 Scala类型的层次结构 Any是所有类...

  • Scala泛型

    泛型类是以类型作为参数,Scala类型参数放在方括号[]中,Java放在<>中 变型 Variance Scala...

  • scala数据类型

    scala数据类型体系图如下 从上图可以得到以下结论: 在 scala 中有一个根类型 Any ,他是所有类的父类...

  • Essential Scala: Literals

    Scala对象系统 总体上,Scala对象系统可分为两类: 引用类型 值类型 引用类型 AnyRef的子类 使用n...

  • Scala学习第四节:Scala 基础数据类型

    数据类型 可以看出 scala 与java 中的类型基本上是一一对应的,不同的是 scala 中的字符类型首字母都...

  • Scala 基本数据类型和操作

    数据类型 Scala 的数据类型和Java是类似的,所有Java的基本类型在scala包中都有对应的类,将Scal...

网友评论

      本文标题:Scala类型类的小应用之CSV Decoder

      本文链接:https://www.haomeiwen.com/subject/avpfoxtx.html