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

Scala类型类的小应用之CSV Encoder

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

by 壮衣

在上一篇博客《Scala由类的动态扩展想到类型类》中提到了类型类,写了一个类型类:Hello[A]并实现了一些类型类的实例。但是为了更容易理解,列举的例子比较简单。这一篇博客我们将对类型类的知识做深入一点的应用:想一下我们需要写一个库用于将任意一种数据类型转换成String类型用于保存为csv文件。在进行数据类型转换时有如下代码:

scala> List(List(1, 2, 3), List(4, 5, 6)).asCsv
res2: String =
1,2,3
4,5,6

scala> List(("zdx", 28), ("ygy", 29)).asCsv
res4: String =
zdx,28
ygy,29

scala> case class Person(name: String, age: Int, height: Double)       
defined class Person

scala> List(Person("zdx", 28, 145.0), Person("ygy", 29, 185.0)).asCsv
res0: String =
zdx,28,145.0
ygy,29,185.0

我们先定义类型类:

trait Encoder[A] {
  def apply(a: A): String
}

然后添加一些基本的类型类实例:

object Encoder {
  def apply[A: Encoder](a: A): String = implicitly[Encoder[A]].apply(a)

  implicit val shortEncoder: Encoder[Short] = new Encoder[Short] {
    override def apply(a: Short): String = a.toString
  }

  implicit val intEncoder: Encoder[Int] = new Encoder[Int] {
    override def apply(a: Int): String = a.toString
  }

  implicit val LongEncoder: Encoder[Long] = new Encoder[Long] {
    override def apply(a: Long): String = a.toString
  }

  implicit val floatEncoder: Encoder[Float] = new Encoder[Float] {
    override def apply(a: Float): String = a.toString
  }

  implicit val doubleEncoder: Encoder[Double] = new Encoder[Double] {
    override def apply(a: Double): String = a.toString
  }

  implicit val charEncoder: Encoder[Char] = new Encoder[Char] {
    override def apply(a: Char): String = a.toString
  }

  implicit val stringEncoder: Encoder[String] = new Encoder[String] {
    override def apply(a: String): String = a
  }

  implicit val booleanEncoder: Encoder[Boolean] = new Encoder[Boolean {
    override def apply(a: Boolean): String = a.toString
  }
}

可以先测试下这些类型类实例:

scala> import org.forcestudy.csvz.Encoder  
import org.forcestudy.csvz.Encoder

scala> import Encoder._
import Encoder._

scala> Encoder[Int](1)                   
res3: String = 1

scala> Encoder[String]("zdx")
res5: String = zdx

scala> Encoder[Boolean](true) 
res8: String = true

为类型类注入方法:

trait EncoderSyntax[A] {
  def asCsv: String
}

object EncoderSyntax {
  implicit def encoderSyntax[A: Encoder](a: A): EncoderSyntax[A] = new EncoderSyntax[A] {
    override def asCsv: String = implicitly[Encoder[A]].apply(a)
  }
}

现在可以再测试下这些类型类的实例:

scala> import org.forcestudy.csvz.EncoderSyntax._
import org.forcestudy.csvz.EncoderSyntax._

scala> 1.asCsv
res9: String = 1

scala> "zdx".asCsv
res10: String = zdx

scala> true.asCsv
res11: String = true

有了这个基本类型的Encoder[A]的实例之后我们可以添加Encoder[List[A]]的实例了:

  implicit def listValEncoder[A <: AnyVal](implicit encoder: Encoder[A]): Encoder[List[A]] = new Encoder[List[A]] {
    override def apply(a: List[A]): String = a.map(encoder.apply).mkString(",")
  }

来测试下Encoder[List[A]]:

scala> Encoder[List[Int]](List(1, 2, 3))
res1: String = 1,2,3

scala> Encoder[List[List[Int]]](List(List(1, 2, 3), List(4, 5, 6)))
<console>:16: error: could not find implicit value for evidence parameter of type org.forcestudy.csvz.Encoder[List[List[Int]]]
       Encoder[List[List[Int]]](List(List(1, 2, 3), List(4, 5, 6)))

在测试Encoder[List[List[Int]]]的时候编译报错, 提示找不到Encoder[List[List[Int]]]的实例,我们有提供Encoder[List[A]] 的实例,但是这个A必须是AnyVal的子类而List[Int]是AnyRef的子类。针对这种情况我还得增加一个实例:

  implicit def listRefEncoder[A <: AnyRef](implicit encoder: Encoder[A]): Encoder[List[A]] = new Encoder[List[A]] {
    override def apply(a: List[A]): String = a.map(encoder.apply).mkString("\n")
  }

好了现在可以测试Encoder[List[List[A]]]了:

scala> import org.forcestudy.csvz.EncoderSyntax._
import org.forcestudy.csvz.EncoderSyntax._

scala> List(List(1, 2, 3), List(4, 5, 6)).asCsv  
res0: String =
1,2,3
4,5,6

scala> List(List('z', 'd', 'x'), List('y', 'g', 'y')).asCsv
res1: String =
z,d,x
y,g,y

再来看下转换元组类型:

scala> Encoder[(String, Int)](("zdx", 29)) 
<console>:19: error: could not find implicit value for evidence parameter of type org.forcestudy.csvz.Encoder[(String, Int)]
       Encoder[(String, Int)](("zdx", 29))

控制台提示我们并没有实现Encoder[(String, Int)]实例,当然我们可以实现Encoder[(String, Int)]实例,那我遇到更多的元组类型呢? 像Encoder[(String, Int, Double)];或者我们该如何处理case class?是不是需要为每种case class都实现类型类实例?假如那样做的话那真是一件繁重的事情,幸好有shapeless,我们将使用shapeless中的Automatic type class instance derivation来实现我们需求,先看下代码:

libraryDependencies ++= Seq(
  "com.chuusai" %% "shapeless" % "2.2.5"
)
object Encoder extends ProductTypeClassCompanion[Encoder] {
  // code
  object typeClass extends ProductTypeClass[Encoder] {
    override def project[F, G](instance: => Encoder[G], to: (F) => G, from: (G) => F): Encoder[F] = new Encoder[F] {
      override def apply(a: F): String = instance.apply(to(a))
    }

    override def emptyProduct: Encoder[HNil] = new Encoder[HNil] {
      override def apply(a: HNil): String = ""
    }

    override def product[H, T <: HList](ch: Encoder[H], ct: Encoder[T]): Encoder[::[H, T]] = new Encoder[::[H, T]] {
      override def apply(a: ::[H, T]): String =
        if (ct.apply(a.tail).isEmpty) ch.apply(a.head)
        else List(ch.apply(a.head), ct.apply(a.tail)).mkString(",")
    }
  }
}

先不深究这一块代码, 我们先来测试下元组的Encoder实例,以及case class的Encoder实例:

scala> import org.forcestudy.csvz.EncoderSyntax._          
import org.forcestudy.csvz.EncoderSyntax._

scala> import org.forcestudy.csvz.Encoder        
import org.forcestudy.csvz.Encoder

scala> import org.forcestudy.csvz.Encoder._
import org.forcestudy.csvz.Encoder._

scala> Encoder[(String, Int)](("zdx", 29))       
res0: String = zdx,29

scala> case class Person(name: String, age: Int, height: Double)
defined class Person

scala> Encoder[Person](Person("zdx", 29, 145.0))                
res1: String = zdx,29,145.0

如上有了shapeless我们并不需要为具体case class和元组添加类型类实例。回到文章的开头我们再来测试下文章开头的几个例子:


image.png

到此代码实现已经可以满足我们的需求了, 但是对于shapeless是如何实现各种case class和元组类型类的实例我们只是列出了代码,并没有做说明,这是另一个问题了,我们留做后面博文说明。

相关文章

  • 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 Encoder

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