逼格極高的編程語言Haskell與范疇論
Haskell 是一門逼格極高的語言,這個(gè)評價(jià)肯定不為過,就我自身的經(jīng)歷及觀察而言,一般初學(xué)者如果沒有相關(guān)函數(shù)式編程的經(jīng)驗(yàn),入門直接接觸那些稀奇古怪的概念,簡直要跪下?,F(xiàn)在回想起來,隱隱覺得初學(xué)者所擁有的命令式語言相關(guān)的知識(shí)和經(jīng)驗(yàn)反而成了負(fù)擔(dān),若能拋掉以往固有的觀念轉(zhuǎn)以全新的視角來看待這些怪東西,仿佛會(huì)更好接受些,真是莫名其妙。
Bartosz Milewski 的博客上寫了很多關(guān)于 C++ 模板 與 Haskell 關(guān)系的相關(guān)文章,讀來真是受益良多,這位大哥很多年前就開始探討 c++ 模板編程與 Haskell 之間的微妙聯(lián)系,許多觀點(diǎn)讓人眼前一亮以至嘆為觀止,比如說從范疇論的角度來理解和解釋什么是單子(monad)(我接下來準(zhǔn)備寫篇博客總結(jié)一下)。Bartosz 講 Haskell 喜歡從數(shù)學(xué)的角度來闡述,視角非同一般,當(dāng)然他不是第一位這樣做的,事實(shí)上 Haskell 與數(shù)學(xué)本來就有著許多不得不說又說不清道不明的曖昧關(guān)系(住口!)。
范疇論基本概念
如果你是第一次聽說范疇論,看到這高大上的名字估計(jì)心里就會(huì)一咯噔,到底數(shù)學(xué)威力巨大,光是高等數(shù)學(xué)就讓很多人噩夢連連。和搞編程的一樣,數(shù)學(xué)家喜歡將問題不斷加以抽象從而將本質(zhì)問題抽取出來加以論證解決,范疇論就是這樣一門以抽象的方法來處理數(shù)學(xué)概念的學(xué)科,主要用于研究一些數(shù)學(xué)結(jié)構(gòu)之間的關(guān)系及聯(lián)系。
在范疇論里,一個(gè)范疇(category)指的是這樣一個(gè)東西,它由三部分組成:
- 一系列的對象(object).
- 一系列的態(tài)射(morphism).
- 一個(gè)組合(composition)操作符,用點(diǎn)(.)表示,用于將態(tài)射進(jìn)行組合。
一個(gè)對象可以看成是一類東西,數(shù)學(xué)上的群,環(huán),甚至簡單的有理數(shù),無理數(shù)等都可以歸為一個(gè)對象,對應(yīng)到編程語言里,可以理解為一個(gè)類型,比如說整型,布爾型,類型事實(shí)上可以看成是值的集合,例如整型就是由 0,1,2...等組成的,因此范疇論里的對象簡單理解就可以看成是值(value)的集合。
一個(gè)態(tài)射指的是一種映射關(guān)系,簡單理解,態(tài)射的作用就是把一個(gè)對象 A 里的值 va 映射為 另一個(gè)對象 B 里的值 vb,這和代數(shù)里的映射概念是很相近的,因此也有單射,滿射等區(qū)分。態(tài)射的存在反映了對象內(nèi)部的結(jié)構(gòu),這是范疇論用來研究對象的主要手法:對象內(nèi)部的結(jié)構(gòu)特性是通過與別的對象的關(guān)系反映出來的,動(dòng)靜是相對的,范疇論通過研究關(guān)系來達(dá)到探知對象的內(nèi)部結(jié)構(gòu)的目的。
組合操作符的作用是將兩個(gè)態(tài)射進(jìn)行組合,例如,假設(shè)存在態(tài)射 f: A -> B, g: B -> C, 則 g.f : A -> c.
看!好像沒有想象中的復(fù)雜!一個(gè)結(jié)構(gòu)要想成為一個(gè)范疇, 除了必須包含上述三樣?xùn)|西,它還要滿足以下三個(gè)限制:
-
態(tài)射要滿足結(jié)合律,即 f.(g.h) = (f.g).h。
-
態(tài)射在這個(gè)結(jié)構(gòu)必須是封閉的,也就是,如果存在態(tài)射 f, g,則必然存在 h = f.g。
-
對結(jié)構(gòu)中的每一個(gè)對象 A, 必須存在一個(gè)單位態(tài)射 Ia: A -> A, 對單位態(tài)射,顯然,對任意其它態(tài)射 f, f.I = f。
講完了!范疇論就這么點(diǎn)東西!-- 當(dāng)然是不可能的,但暫時(shí)來說,知道這些就已經(jīng)很足夠了。
Haskell 中的范疇
在 Haskell 中存在著這樣一個(gè)唯一的范疇,名字稱為 Hask, 這個(gè) Hask 滿足前面關(guān)于范疇的全部約定,因此是范疇論里一個(gè)純正的“范疇":
-
對象就是 Haskell 里的所有類型,記得類型是一個(gè)集合。
-
態(tài)射就是編程語言里的一般函數(shù)(function),如:
func :: Int -> Bool
,將對象 int 映射為 對象 bool。 -
態(tài)射的組合就是函數(shù)的組合,在 Haskell 里,函數(shù)也是通過點(diǎn)號(.)進(jìn)行組合的。
另外三個(gè)約束條件很容易證明也是滿足,因此整個(gè) Haskell 從數(shù)學(xué)的角度上看它就是一個(gè)范疇,這個(gè)角度的理解是很深刻的,這樣一來傳統(tǒng)意義上諸如語法,類型,函數(shù)等語言特性其實(shí)都只是這個(gè)內(nèi)在本質(zhì)的外在表現(xiàn)而已。
函子
前面對范疇的介紹反映了范疇內(nèi)部各個(gè)對象之間的聯(lián)系與相互作用,在范疇論里另外研究的重點(diǎn)是范疇與范疇之間的關(guān)系,就正如對象與對象之間有態(tài)射一樣,范疇與范疇之間也存在某些映射,從而可以將一個(gè)范疇映射為另一個(gè)范疇,這種映射在范疇論中叫作函子(functor),具體來說,對于給定的兩個(gè)范疇 A 和 B, 函子的作用有兩個(gè):
-
將范疇 A 中的對象映射到范疇 B 中的對象。
-
將范疇 A 中的態(tài)射映射到范疇 B 中的態(tài)射。
顯然,函子反映了不同的范疇之間的內(nèi)在聯(lián)系,函子的定義是十分松散的,而不同范疇之間的關(guān)系有強(qiáng)有弱,一個(gè)隨便定義的函子很多時(shí)候并不能太深刻反映范疇之間結(jié)構(gòu)上的聯(lián)系,因此數(shù)學(xué)上,對函子通常有幾個(gè)限制,先假設(shè) F 是范疇 A 與范疇 B 上一個(gè)函子,則:
-
對范疇 A 上的單位態(tài)射Ia, F 必須將其映射為范疇 B 上的單位態(tài)射 Ib, F(Ia) = Ib.
-
函子對態(tài)射的組合必須滿足分配徤,即,假設(shè) f, g 是范疇 A 上的態(tài)射,則 F(f.h) = F(f).F(g)。
顯然這兩個(gè)限制是很強(qiáng)的,如果兩個(gè)范疇之間存在這樣一個(gè)函子,則反映了他們之間在結(jié)構(gòu)上有著很強(qiáng)的相似性,從看似風(fēng)牛馬不相及的東西里找出他們內(nèi)在的相似性,數(shù)學(xué)家最愛干的事情了。
和態(tài)射一樣函子也可以是自映射的,即函子允許將范疇映射到其自身,這樣做有什么好處呢?不同范疇之間的映射反映了范疇間的相似性,范疇到范疇自身的映射則顯然是反映了范疇內(nèi)部的自相似性 --- 到底認(rèn)識(shí)自己也不是一件容易的事啊。。。自相似性是大自然里美妙的存在,想想六角形的雪花,想想分形... 在范疇論里,這種將范疇映射到自身的函子被稱為自函子(endofunctor).
Haskell 中的函子
知道為什么要講自函子了嗎,Haskell 中只有一個(gè)范疇! 那么這個(gè)唯一的范疇 Hask 中,存不存在自函子呢?有的!終于講到重點(diǎn)了,為什么 Haskell 有這么些奇怪的概念? Haskell 的老鳥會(huì)告訴你,這些奇怪的東西都是寶貝,它們都是有本而來的。
那么 Haskell 中的自函子是怎么體現(xiàn)出來的呢? 根據(jù)前面的定義,一個(gè)函子其實(shí)就是一個(gè)映射,它把對象映射為對象,把態(tài)射映射為態(tài)射,我們知道在 Haskell 中對象就是一個(gè)類型,如整型,布爾型等,將一個(gè)類型映射為另一個(gè)類型,沒錯(cuò),就是 type constructor 在干的事情,c++ 的程序員可以用模板類來想象一下,如,vector<int>
就是將 int
映射為 vector<int>
, 這是兩種不同的類型了,實(shí)例化模板的過程實(shí)際就是把一個(gè)類型變成另一個(gè)類型的過程。
注意不要把對象的映射與對象內(nèi)部的態(tài)射混淆了,態(tài)射是將對象內(nèi)部的值進(jìn)行映射,而對象的映射(函子)是把對象這個(gè)整體映射為另一個(gè)對象,函子根本不關(guān)心一個(gè)對象內(nèi)部會(huì)有什么值。
顯然我們可以看到,在 Haskell 中,類型到類型的映射事實(shí)上并不是普遍存在的,自函子反映的是范疇內(nèi)部的結(jié)構(gòu)關(guān)系,這些關(guān)系并不是因?yàn)楹拥拇嬖诙嬖?,函子只是揭示了這些內(nèi)在的關(guān)系。具體在 Haskell 中,類型間的關(guān)系并不是普遍存在的,比如說, Int -> Bool 就沒有對應(yīng)的映射關(guān)系,而存在映射關(guān)系的類型,它們都有一些共同的特點(diǎn),映射雙方可以看成是由簡單的類型轉(zhuǎn)變?yōu)閺?fù)雜的類型。
type constructor 就是自函子的一部分!
好了,現(xiàn)在類型到類型的映射在 Haskell 中找到了,那態(tài)射到態(tài)射之間的映射呢?必竟這也是函子的必要組成部分。
在 Haskell 中,態(tài)射就是一般的函數(shù),把一個(gè)函數(shù)映射為另一個(gè)函數(shù),聽起來不就是高階函數(shù)在干的事情嘛。具體來說,映射函數(shù)這件事發(fā)生在 Functor 這個(gè) typeclass 里,連名字都一模一樣,目的昭然若揭。Haskell 中 Functor 是一個(gè) typeclass,它的定義如下:
- class Functor f where
- fmap:: (a -> b) -> f a -> fb
fmap 干嘛的?顯然就是用于把態(tài)射 (a -> b)
映射為態(tài)射 (f a -> f b)
的,它把范疇里的態(tài)射映射到另一個(gè)態(tài)射,且遵守了函子在映射態(tài)射時(shí)所需要遵守的兩個(gè)原則。
講到這里,我們一步一步不知不覺就已經(jīng)向著 monad 靠近了,好激動(dòng),先打住了,回頭再整理整理。
【參考】
http://en.wikibooks.org/wiki/Haskell/Category_theory
http://bartoszmilewski.com/2011/01/09/monads-for-the-curious-programmer-part-1/