Scala型別類的小應用之Functor Foldable

weixin_33850890發表於2018-01-14

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社群中的ScalaZCats對這些typeclass都有做實現,我後續博文也會展開介紹,敬請關注。

相關文章