Scala型別類的小應用之Functor Foldable
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都有做實現,我後續博文也會展開介紹,敬請關注。
相關文章
- Scala由類的動態擴充套件想到型別類套件型別
- [譯] Scala 型別的型別(四)型別
- [譯] Scala 型別的型別(二)型別
- [譯] Scala 型別的型別(三)型別
- [譯] Scala 型別的型別(六)型別
- [譯] Scala 型別的型別(五)型別
- scala_繼承、型別判斷、抽象類、匿名內部類繼承型別抽象
- Scala 泛型型別和方法泛型型別
- Scala(一)資料型別資料型別
- 【Scala之旅】型別引數型別
- 探索Scala(5)-- 基本型別型別
- Scala結構型別與複合型別解析型別
- scala片段4:使用型別類實現Pimp my library模式型別模式
- Scala 中的集合(一):集合型別與操作型別
- 達夢6.0試用之資料型別資料型別
- Scala的泛型泛型
- Scala學習(五)---Scala中的類
- 交易型應用與消費類應用的區別 | infoworld
- 【Scala之旅】特質與高階型別型別
- scala和java資料型別轉換Java資料型別
- PHP類方法的型別提示PHP型別
- scala中:: , +:, :+, :::, +++的區別
- php運算子運用之型別運算子該如何使用PHP型別
- 利率型別_小記型別
- Scala 類和物件物件
- Scala呼叫Java類Java
- scala類和物件物件
- 泛型類、泛型方法、型別萬用字元的使用泛型型別字元
- c#之tcbs靜態方法_返回值為類的型別_小記C#型別
- 海爾的JIT應用之旅
- DM 類資料型別資料型別
- 原子更新基本型別類型別
- 型別轉換工具類型別
- What is functor in Haskell ?Haskell
- c++ 泛型 程式設計 之 Functor 設計模式C++泛型程式設計設計模式
- Oracle資料型別對應Java型別Oracle資料型別Java
- scala中的匿名子類實現
- Scala的類、屬性、物件欄位物件