函子與范疇
函子(functor)是從一個范疇到另一個范疇的轉(zhuǎn)換,并且其亦可轉(zhuǎn)換/保持態(tài)射(morphism)。
一個態(tài)射是從一個范疇里的一個值到同一個范疇里的另一個值的變換。在貓的范疇的例子里,一個態(tài)射好比一個盒子,能夠把黯淡無光的貓轉(zhuǎn)化為一個霓虹閃耀的貓勋磕。在類型的范疇里(計算機科學(xué)常用的范疇),一個態(tài)射是一個把某類型轉(zhuǎn)化為另一個類型的函數(shù)敢靡。
函子是可以把貓轉(zhuǎn)化為狗的東西(不同范疇的轉(zhuǎn)換)挂滓。函子可以把暗淡的貓轉(zhuǎn)換成暗淡的狗,光彩的貓轉(zhuǎn)換成光彩的狗啸胧。函子還可以連態(tài)射都轉(zhuǎn)換過去赶站,這樣可以把暗淡的狗轉(zhuǎn)化為光彩的狗。
圖中纺念,下面的圈子表示所有的類型組成的范疇贝椿。里面包括標(biāo)準的String、Double和其他Scala能定義的類型陷谱。函子F是Scala里的一個類型構(gòu)造器烙博。對于一個范疇中的任意類型T,可以把該類型置于類型構(gòu)造器
F[_]
中烟逊,從而得到一個新類型F[T]
渣窜。
對函子的描述
函子本質(zhì)上是范疇之間的轉(zhuǎn)換。
上圖表示范疇C到范疇D的函子宪躯。圖中乔宿,對象A和B被轉(zhuǎn)換到了范疇D中同一個對象,因此访雪,態(tài)射g就被轉(zhuǎn)換成了一個源對象和目標(biāo)對象相同的態(tài)射(不一定是單位態(tài)射)详瑞,而且id_A和id_B變成了相同的態(tài)射掂林。對象之間的轉(zhuǎn)換是用淺黃色的虛線箭頭表示,態(tài)射之間的轉(zhuǎn)換是用藍紫色的箭頭表示坝橡。
范疇之間的轉(zhuǎn)換必須在轉(zhuǎn)換時保留所有的態(tài)射才能稱為函子轉(zhuǎn)換泻帮。也就是說,如果在第一個范疇有個操作類型的函數(shù)驳庭,那么我們也應(yīng)該有個轉(zhuǎn)換后的函數(shù)能夠操作轉(zhuǎn)換后的類型刑顺。比如說,如果我們有個吧String轉(zhuǎn)換為Int的函數(shù)饲常,那么我們也應(yīng)該能夠把F[String]轉(zhuǎn)換成F[Int],這正是map方法所提供的功能狼讨。
trait Functor[F[_]] {
def apply[A](x: A): F[A]
def map[A, B](x: F[A])(f: A => B): F[B]
}
apply方法的作用——對于任意類型A贝淤,一個函子能夠在新范疇里構(gòu)造一個類型F[A]。
map方法的作用——給定一個轉(zhuǎn)換后的類型F[A]和一個在原范疇里的態(tài)射A => B政供,能夠創(chuàng)建一個F[B]類型的結(jié)果播聪。也就是說,我們有個新函數(shù)能夠接受F[A]布隔,返回F[B]离陶。
簡單的說,函子是實現(xiàn)了map方法的數(shù)據(jù)類型衅檀。
舉例來說招刨,
trait Functor[F[_]] {
def fmap[A, B](x: F[A])(f: A => B): F[B]
}
object ListFunctor extends Functor[List] {
def fmap[A, B](list: List[A])(f: A => B): List[B] = list map f
}
val l1 = List(1, 2, 3)
val result = ListFunctor.fmap(l1)(i => i+1)
println(result)
// print is
// List(2, 3, 4)
函子的形象演示
假設(shè)有一個容器(container),這里的容器可以認為是一種上下文(context)或者是為函數(shù)提供的計算上下文(computational context)哀军。
在圖中表示為一個盒子沉眶。當(dāng)一個元素被上下文包裹,我們不能簡單的對該元素應(yīng)用一般的運算方法杉适,于是需要使用map函數(shù)(Haskell中稱為fmap)谎倔。
map函數(shù)知道如何將普通函數(shù)應(yīng)用到一個被上下文包裹的元素上。
函子(functor)是定義了如何應(yīng)用map的類型類(typeclass)猿推。
如果我們要將普通函數(shù)應(yīng)用到一個有盒子上下文包裹的值片习,那么我們首先需要定義一個叫Functor的數(shù)據(jù)類型,在這個數(shù)據(jù)類型中需要定義如何使用map或fmap來應(yīng)用這個普通函數(shù)蹬叭。
函子內(nèi)部工作原理可以認為是這樣的:
- 將值從上下文盒子中解救出來
- 將外部指定的函數(shù)(+3)應(yīng)用到這個值上藕咏,得到一個新的值(5)
- 再將這個新值放入到上下文盒子中
下圖顯示了如何將一個普通函數(shù)應(yīng)用到值集合,不是單個值具垫,而是值的集合數(shù)組中侈离,圖中數(shù)組函子將數(shù)組一個個打開(遍歷),然后分別將普通函數(shù)應(yīng)用到這些元素中筝蚕,最后返回一個新的集合值卦碾。
補充:高階類型
基本泛型:
如果類型參數(shù)也是一個泛型(類型構(gòu)造器):
對類型的歸納:
類型(type)是對數(shù)據(jù)的抽象铺坞,而高階類型(higher-kinded type)是對類型的抽象:
參考資料
函數(shù)編程中functor和monad的形象解釋
深入理解Scala
理解高階類型
Scala和范疇論 -- 對Monad的一點認識
轉(zhuǎn)載請注明作者Jason Ding及其出處
jasonding.top
Github博客主頁(http://blog.jasonding.top/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡書主頁(http://www.reibang.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進入我的博客主頁