by 壮衣
在之前的博文《Scala类型类的小应用之CSV Encoder》中有一段代码:
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(",")
}
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[A <: AnyVal]] 和Encoder[List[A <: AnyRef]],有了这两个类型类实例就可以完成List[Int]和
List[(Int, Int, Int)]转换成String:
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)))
res2: String =
1,2,3
4,5,6
那么我们想处理Vector[Int]类型时,该何如处理?当然我们可以构建Vector[A <: AnyVal的Encoder实例:
implicit def vectorValEncoder[A <: AnyVal](implicit encoder: Encoder[A]): Encoder[Vector[A]] = new Encoder[Vector[A]] {
override def apply(a: Vector[A]): String = a.map(encoder.apply).mkString(",")
}
测试一下Enocder[Vector[A <: AnyVal]]:
scala> Encoder[Vector[Int]](Vector(1, 2, 3))
res0: String = 1,2,3
仔细对比listValEncoder和vectorValEncoder方法会发下两者实现除了List和Vector类型有区别,其它的实现都是一样的,那我们是否可以将两个方法合并呢?熟悉Scala类型系统的话可以知道List和Vector都是继承与Seq,那么可以用Seq来代替List和Vector,只需要实现Encoder[Seq[A <: AnyVal]]就好了:
implicit def seqValEncoder[A <: AnyVal](implicit encoder: Encoder[A]): Encoder[Seq[A]] = new Encoder[Seq[A]] {
override def apply(a: Seq[A]): String = a.map(encoder.apply).mkString(",")
}
让我们来测试下Encoder[Vector[A]]和Encoder[List[A]]:
scala> Encoder[Vector[Int]](Vector(1, 2, 3))
<console>:16: error: could not find implicit value for evidence parameter of type org.forcestudy.csvz.Encoder[Vector[Int]]
Encoder[Vector[Int]](Vector(1, 2, 3))
^
scala> Encoder[List[Int]](List(1, 2, 3))
<console>:16: error: could not find implicit value for evidence parameter of type org.forcestudy.csvz.Encoder[List[Int]]
Encoder[List[Int]](List(1, 2, 3))
^
scala> Encoder[Seq[Int]](Seq(1, 2, 3))
res2: String = 1,2,3
可以看到Vector[Int]和List[Int]都转换失败了,只有Seq[Int]转换成功,可见子类型多态(Subtype polymorphism)在这里是行不通的,就算可以如何处理Set[Int]或者Option[Int]?两者可都不是Seq的子类。我们再看下listValEncoder和vectorValEncoder的实现,发现两者都是做了一个map操作把List[A]和Vector[A]转换成List[String]和Vector[String],然后调用mkString方法把List[String]和Vector[String]转换成String。可以看到这其中就是做了一个map转换操作和一个mkString的折叠操作,这两个操作能否抽象成类型类呢?当然是可以,我们先抽象一个转换操作的类型类:
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
object Functor {
def apply[F[_]: Functor]: Functor[F] = implicitly[Functor[F]]
}
再来抽象一个折叠操作的类型类:
trait Foldable[F[_]] {
def foldRight[A, B](fa: F[A], b: B)(f: (A, B) => B): B
def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B
}
object Foldable {
def apply[F[_]: Foldable]: Foldable[F] = implicitly[Foldable[F]]
}
好了现在我们可以去掉listValEncoder和vectorValEncoder方法了,利用Functor和Foldable可以写一个更加通用的方法valEncoder来实现Encoder[List[A]]和Encoder[Vector[A]],甚至是Encoder[Set[A]]和Encoder[Option[A]]等类型, 来看下代码:
implicit def valEncoder[F[_]: Functor: Foldable, A <: AnyVal: Encoder]: Encoder[F[A]] = new Encoder[F[A]] {
override def apply(a: F[A]): String = {
val fs = Functor[F].map(a)(implicitly[Encoder[A]].apply)
Foldable[F].foldLeft(fs, "") {
(a, b) => if (a.isEmpty) a + b else a + "," + b
}
}
}
好了, 我们来测试下Encoder[Vector[A]]:
scala> Encoder[Vector[Int]](Vector(1, 2, 3))
<console>:16: error: could not find implicit value for evidence parameter of type org.forcestudy.csvz.Encoder[Vector[Int]]
Encoder[Vector[Int]](Vector(1, 2, 3))
出现了编译错误, 显然我们没有实现vector和list的Functor实例和Foldable实例,来看下如何实现
object Functor {
def apply[F[_]: Functor]: Functor[F] = implicitly[Functor[F]]
implicit val vectorFuncor: Functor[Vector] = new Functor[Vector] {
override def map[A, B](fa: Vector[A])(f: (A) => B): Vector[B] = fa.map(f)
}
implicit val listFuncor: Functor[List] = new Functor[List] {
override def map[A, B](fa: List[A])(f: (A) => B): List[B] = fa.map(f)
}
}
object Foldable {
def apply[F[_]: Foldable]: Foldable[F] = implicitly[Foldable[F]]
implicit val vectorFoldable: Foldable[Vector] = new Foldable[Vector] {
override def foldRight[A, B](fa: Vector[A], b: B)(f: (A, B) => B): B =
fa.foldRight(b)(f)
override def foldLeft[A, B](fa: Vector[A], b: B)(f: (B, A) => B): B =
fa.foldLeft(b)(f)
}
implicit val listFoldable: Foldable[List] = new Foldable[List] {
override def foldRight[A, B](fa: List[A], b: B)(f: (A, B) => B): B =
fa.foldRight(b)(f)
override def foldLeft[A, B](fa: List[A], b: B)(f: (B, A) => B): B =
fa.foldLeft(b)(f)
}
}
现在再来测试下Encoder[Vector[A]]和Encoder[List[A]]:
scala> Encoder[Vector[Int]](Vector(1, 2, 3))
res0: String = 1,2,3
scala> Encoder[List[Int]](List(1, 2, 3))
res1: String = 1,2,3
同理我们可以添加Functor[Set]、Functor[Option]和Foldable[Set]和Foldable[Option]的实例:
implicit val setFuncor: Functor[Set] = new Functor[Set] {
override def map[A, B](fa: Set[A])(f: (A) => B): Set[B] = fa.map(f)
}
implicit val optionFuncor: Functor[Option] = new Functor[Option] {
override def map[A, B](fa: Option[A])(f: (A) => B): Option[B] = fa.map(f)
}
implicit val setFoldable: Foldable[Set] = new Foldable[Set] {
override def foldRight[A, B](fa: Set[A], b: B)(f: (A, B) => B): B =
fa.foldRight(b)(f)
override def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B =
fa.foldLeft(b)(f)
}
implicit val optionFoldable: Foldable[Option] = new Foldable[Option] {
override def foldRight[A, B](fa: Option[A], b: B)(f: (A, B) => B): B =
fa.foldRight(b)(f)
override def foldLeft[A, B](fa: Option[A], b: B)(f: (B, A) => B): B =
fa.foldLeft(b)(f)
}
测试一下Encoder[Set[A]]和Encoder[Option[A]]:
scala> Encoder[Set[Int]](Set(1, 2, 3))
res4: String = 1,2,3
scala> Encoder[Option[Int]](Some(1))
res5: String = 1
上面针对的类型都是A <: AnyVal,对于类型A <: AnyRef可同样抽象出一个通用的refEncoder方法,代码如下:
implicit def refEncoder[F[_]: Functor: Foldable, A <: AnyRef: Encoder]: Encoder[F[A]] = new Encoder[F[A]] {
override def apply(a: F[A]): String = {
val fs = implicitly[Functor[F]].map(a)(implicitly[Encoder[A]].apply)
implicitly[Foldable[F]].foldLeft(fs, "") {
(a, b) => if (a.isEmpty) a + b else a + "\n" + b
}
}
}
这样我们来测试下Encoder[Option[List[Int]]]和Encoder[Vector[Person]]:
scala> Encoder[Option[List[Int]]](Some(List(1, 2, 3)))
res6: String = 1,2,3
scala> Encoder[Vector[Person]](Vector(Person("zdx", 29, 145.0), Person("ygy", 28, 185.0)))
res8: String =
zdx,29,145.0
ygy,28,185.0
可见利用Functor和Foldable,我们可以写出更加通用的方法,例如refEncoder方法既可以适用于List、Vector,也可以适用于Set和Option甚至更多的类型只要我们实现类型对应的Functor和Foldable的实例,其实这也是一种多态称之为Ad-hoc polymorphism,区别于Subtype polymorphism。对于Decoder类型类我们也可以使用相同方案来抽象更为通用的代码,这个在后面的博文会写到。Functor、Foldable这些都是数学分支范畴论中的概念,Scala社区中的ScalaZ和Cats对这些typeclass都有做实现,我后续博文也会展开介绍,敬请关注。