上一篇:聲明式編程html
原文地址:https://bartoszmilewski.com/2...編程
彷佛在範疇論中全部事情都是相互聯繫的,而且均可以用不少角度觀察。就拿積的泛構造來舉例吧,既然咱們已經對函子和天然變換瞭解更多了,咱們能不能簡化積的泛構造,或者也許,推廣它?讓咱們試試。segmentfault
積的構造始於選取咱們想要構造積的兩個對象a
和b
。但選擇對象到底是什麼意思?咱們可否用更加範疇論的語言從新描述這個動做?兩個對象構成了一個模式——很是簡單的模式。咱們能夠把這種模式抽象成一個範疇——一個很是簡單的範疇,即使如此也是個範疇。咱們稱之爲2。它包含了兩個對象,1和2,而且除了必要的兩個恆等態射外沒有其餘態射。如今咱們把在C中選取兩個對象重述爲定義了一個從2到C的函子D。函子把對象映爲對象,因此它的像就只是兩個對象而已(或者若是這個函子坍縮了對象的話,也能夠是一個,那也是ok的)。它也會映射態射——這裏就僅僅是把恆等態射映爲恆等態射。安全
這種方式的好處是創建在範疇的概念上,從而避免了諸如「選擇對象」這樣不精確的,狩獵採集時代的祖先們就在用的描述。不只如此,它還順帶了一個易推廣的好處,由於徹底能夠用些比2更加複雜的範疇來定義咱們的模式。markdown
不過,先讓咱們繼續(構造積)。定義一個積的下一步是選取候選對象c
。一樣地,咱們能夠用一個從單例範疇出發的函子來重述這個選取。若是咱們用Kan延拓(Kan extension,中文不知道叫什麼,有知道的童鞋告訴我啊,譯者注)的話,確實不會有什麼問題,可咱們還不能講Kan延拓,因此咱們用另外一種技巧:從同一個2範疇到C的常函子Δ。在C中選取一個c
能夠用Δc。記住,Δc把全部對象都映爲c
而且把全部態射都映爲idc。架構
如今咱們有了兩個從2到C的函子Δc和D,因此徹底能夠求求它們之間的天然變換。由於2中只有兩個對象,那麼一個天然變換就會有兩個份量。2的對象1被Δc映爲c
而被D映爲a
。因此Δc和D之間的天然變換在1上的份量就是從c
到a
的態射。咱們把它記做p
。一樣地,第二個份量是從c
到b
的態射q
——b
是2的對象2在D下的像。這就像極了咱們以前原始的積的定義中的那兩個投影。因此咱們再也不說選取對象和投影這樣的話了,而是說選取函子和天然變換。在這種簡單的情形下變換的天然性以一種平凡的方式知足了,由於2中根本沒有非平凡的態射(除了恆等態射)。app
若是把這種結構推廣到不是2的範疇——例如,包含了非平凡態射的範疇——那麼就要加上Δc和D天然變換的天然性條件。咱們把這樣的變換叫作一個錐,由於Δ的像就像錐/金字塔的頂點,而錐的腰由天然變換的份量組成。而D的像就是這樣一個錐的底。函數
通常來講,爲了構造一個錐,咱們會從一個定義了某種模式的範疇I出發。它是一個小范疇,一般來講都是有限的。咱們選取一個從I到C的函子D稱之(或D的像)爲一個圖表。咱們選取C中的某個c
做爲錐的頂點,並用它定義從I到C的常函子Δc。而後,Δc到D的天然變換就是咱們的錐了。對於一個有限大的I,錐就只是一族鏈接c
到圖表的態射:而圖表是I在D下的像。ui
天然性須要保證圖中全部的三角形(也就是金字塔的面)是交換的。事實上,選擇I的任意態射f
,函子D會把它映爲C中的一個態射D f
,它是某個三角形的底。而常函子Δc會把f
映爲c
上的恆等態射。Δ把態射的兩個端壓爲一個對象,而後四方交換圖就變成了交換三角形。這個三角形的兩臂就是天然變換的份量。spa
這就是一個錐了。咱們感興趣的是那個泛錐(universal cone)——就像咱們從積定義中選了一個最普適的對象那樣。
作這件事有不少方法。好比,給定一個函子D,咱們能夠定義一個錐範疇。範疇中的對象就是錐。然而並非C中的每一個c
均可以成爲一個錐的頂點,由於從Δc和D之間可能不存在天然變換。
要讓其成爲一個範疇,咱們還須要定義錐之間的態射。而它們會被錐頂點之間的態射所徹底決定。但並非全部這樣的態射均可以。回憶一下,在咱們的積的構造裏,咱們引入了一個條件:候選對象(頂點)之間的態射必須是各個投影的公因子。好比:
p' = p . m q' = q . m
在通常的情形下,這個條件就是說那些以「因子態射」做爲一條邊的三角形都要可交換。
鏈接兩個錐的可交換三角形,h是因子態射(這裏,比較低的錐是更「泛」的,它以Lim D
爲頂點)上面這句話是圖注。因爲思否的markdown基本不支持html,以後個人圖注都會以引用來代替。(第一部分的譯者也是如此作的)譯者注。
咱們就把這些因子態射做爲錐範疇中的態射。很容易檢查這些態射能夠複合,而且(錐範疇的)恆等態射就是恆等因子態射。所以錐能夠造成一個範疇。
如今咱們就能夠定義泛錐了。他就是錐範疇的終端對象。終端對象的定義是說全部其餘對象到它都有一個惟一的態射。具體來講,就是其餘錐的頂點都有一個惟一的因子態射指向泛錐的頂點。這個泛錐就稱做圖表D的極限,Lim D
(在印刷體裏,你會常常看到在Lim
符號下面有個指向I的左箭頭)。一般,爲了方便,咱們就把泛錐的頂點叫極限(或極限對象)
關於
Lim
下的左箭頭,譯者目前沒有在網上找到相應的印刷體資料,但左箭頭總以爲怪怪的。猜想頗有可能實際上是右箭頭。譯者注。
直覺上來講極限把整個圖表的性質體如今單個的對象上。例如,咱們的雙對象圖表的極限就是兩個對象的積。積(和兩個投影一塊兒)包含了兩個對象的全部信息,而且它是泛的,也就是它不包含冗餘信息。
仍然有一些東西是如今這個極限的定義不能知足的。個人意思是,如今的極限定義已經可使用了,但咱們須要鏈接任意兩個錐的三角形們的交換性條件。若是咱們能把它替換成某個天然性條件的話就優雅多了。但這要怎麼操做呢?
咱們再也不討論單獨的錐而是關注一整個錐集(實際上,是錐範疇)。若是極限存在(——說的清楚點——如今不能保證它必定存在),那麼這些錐當中就有那個泛錐。對於每一個其餘的錐咱們有一個惟一的因子態射來把它的頂點c
映爲泛錐的頂點Lim D
。(實際上,我能夠省略「其餘」這個詞,由於有恆等態射把泛錐映到它本身,即泛錐自己平凡地因式化了本身。)讓我重複一下重點:給定任意一個錐,那就有惟一的某個具體的態射。咱們就有了從錐到具體的態射的映射,而且是一一映射。
這個具體的態射是hom集C(c, Lim D)
裏的某個傢伙。這個hom集裏的其餘夥伴就很不幸了,個人意思是它們不能因式化錐之間的映射。咱們想要作的是對於每個c
,可以從集合C(c, Lim D)
裏挑出一個態射——一個可以知足特殊的交換性條件的態射。這聽起來不是很像定義一個天然變換嗎?確實是這樣!
但和這個變換相關的函子是什麼?
一個是c
到集合C(c, Lim D)
的映射。這是一個從C到Set的函子——它把對象映爲集合。
這實際上是一個逆變函子。接下來是定義它在態射上的行爲:咱們取一個c'
到c
的態射f
:
f :: c' -> c
咱們的函子把c'
變成了C(c', Lim D)
。要定義函子在f
上的行爲(換句話說,要提高f
),咱們必須定義C(c, Lim D)
和C(c', Lim D)
之間的映射。咱們選取C(c, Lim D)
中的一個元素u
而後看看可否把它映爲C(c', Lim D)
裏的某個元素。hom集的元素是態射,因此咱們有:
u :: c -> Lim D
咱們把u
在f
前複合一下就獲得了:
u . f :: c' -> Lim D
這就是C(c', Lim D)
的一個元素了——因此咱們確實找到了一個態射的映射:
contramap :: (c' -> c) -> (c -> Lim D) -> (c' -> Lim D) contramap f u = u . f
注意逆變函子的特徵:c
和c'
的順序是反的。
要定義一個天然變換,咱們還須要另外一個從C映到Set的函子。但這一次咱們考慮錐的集合。錐就是天然變換而已,因此咱們來看看天然變換的集合Nat(Δ_c, D)
。從c
到這個特殊的天然變換集之間的映射是一個(逆變)函子。咱們怎麼證實?一樣,讓咱們定義它對態射的行爲:
f :: c' -> c
f
的提高應該是關於I到C的兩個函子間天然變換(全體)的映射:
Nat(Δ_c, D) -> Nat(Δ_c', D)
咱們怎麼樣映射天然變換(全體)呢?每一個天然變換都是一種態射的選取——也就是它的份量——每一個I的元素選一個態射。某個α(Nat(Δ_c, D)
裏的一個傢伙)在a
(I的一個對象)上的份量是一個態射:
α_a :: Δ_c(a) -> D a
或者,帶入常函子Δ的定義,
α_a :: c -> D a
給定f
和α,咱們必須構造一個Nat(Δ_c', D)
的元素β,它在a
的份量也應該是一個映射
β_a :: c' -> D a
咱們很容易就能夠把前者在f
前複合一下來獲得後者:
β_a = α_a . f
把這些份量合起來就是一個天然變換也是很容易證實的。
給定一個態射f
,咱們就能這樣按份量構造出兩個天然變換間的映射。而這個映射就定義了該函子的contramap
:
c -> Nat(Δ_c, D)
我所作的只是向你展現了兩個C到Set的(逆變)函子。我尚未作任何假設——這些函子老是存在的。
附帶一提,第一個函子在範疇論中扮演了重要的角色,咱們會在講到米田引理的時候再次看到它。這種從任意範疇C到Set的逆變函子有個名字:「預層」。第一個仍是一個可表預層。第二個函子也是個預層。
如今咱們有兩個函子了。咱們能夠聊聊它們的天然變換了。好,再也不囉嗦別的,結論是:一個I到C的函子D
有一個極限Lim D
當且僅當我剛剛定義的這兩個函子間有個天然同構。
C(c, Lim D) ≃ Nat(Δ_c, D)
我來提醒你一下天然同構是什麼。天然同構就是一個每一個份量都是一個同構——也就是一個可逆的態射——的天然變換。
我不打算證實這個陳述。若是不用冗長的證實,那這個過程很是直接。當處理天然變換時,你一般會關注份量,它們就是些態射。這裏,由於兩個函子的終點都是Set,因此天然同構的份量就會是些函數,它們是些高階函數,由於它們從hom集映到天然變換的集合。又一次,你能夠分析一個函數,看看它對它的參數作了什麼:這裏的參數是個態射——一個C(c, Lim D)
的傢伙——它的結果是個天然變換——一個Nat(Δ_c, D)
的傢伙,或者是咱們說的錐。而後,這個天然變換的份量又是一些態射。因此作到底以後它們都是態射,若是你能跟蹤它們,你就能證實這個結論。
最重要的結果是這個同構的天然性條件就剛恰好是錐映射的交換性條件。
做爲一些更具誘惑力的東西的鋪墊,咱們說集合Nat(Δ_c, D)
能夠當作是函子範疇裏的hom集;因此咱們的天然同構關聯了兩個hom集,這揭露一種甚至更廣泛的關係,伴隨。
咱們已經看到了範疇積就是由一個簡單的叫作2的範疇生成的圖表的極限。
有一個甚至更爲簡單的極限的例子:終端對象。你的第一反應多是想用單例範疇導出終端對象,但事實比那還簡單:終端對象是由空範疇生成的極限。一個空範疇上的函子不會選擇任何對象,因此錐就僅僅縮成了頂點。泛錐就是那個從其餘頂點出發有惟一的態射到達的孤單的頂點。你能夠看到這就是終端對象的定義。
下一個有意思的極限叫等化子。它是一個由含有兩個對象的範疇生成的極限,這個範疇在那兩個對象上有兩個平行態射(以及,必定會有的,恆等態射)。這個範疇在C中選擇了一個由兩個對象a
和b
以及兩個態射f
和g
組成的圖表:
f :: a -> b g :: a -> b
爲了經過這個圖標構建一個錐,咱們必須加上頂點c
和兩個投影:
p :: c -> a q :: c -> b
咱們有兩個必須交換的三角形:
q = f . p q = g . p
這告訴咱們q
能夠被其中一個式子惟一地決定,好比q = f . p
,而且咱們能夠把它從圖中省略。所以剩下的就只有一個條件:
f . p = g . p
考慮這個條件的方法以下,若是咱們只關注Set,那麼函數p的像會選擇a
的一個子集。當限制在這個子集上時,f
和g
是相等的。
好比,取a
爲由x
和y
參數化的二維平面,取b
爲實直線,而後取:
f (x, y) = 2 * y + x g (x, y) = y - x
這兩個函數的等化子就是實數集(也就是那個頂點c
)和函數:
p t = (t, (-2) * t)
注意(p t)
定義了二維平面上的一條直線。在這條直線上,這兩個函數相等。
固然,還有其餘的集合c'
和函數p'
能夠知足等式:
f . p' = g . p'
但它們會被p
唯一地因式化。好比說,咱們能夠取單例集()
做爲c'
和函數
p' () = (0, 0)
這是一個好的錐,由於 f (0, 0) = g (0, 0)
。但它並非泛的,由於它能夠經過h
惟一的因式化:
p' = p . h h () = 0
所以一個等化子就能解形如f x = g x
的方程。但它更加通常,由於它是從對象和態射的角度定義的,而不是用代數學。
一個更加通常的解方程的思路體如今另外一個極限上——回拉。咱們仍然有想要讓其相等的兩個態射,但此次它們的定義域不一樣。咱們從一個形狀像1->2<-3
的三對象範疇出發。這個範疇對應的圖表有三個對象a
,b
和c
,以及兩個態射:
f :: a -> b g :: c -> b
這個圖表一般被稱做一個餘擴張。
在這個圖表上構造的錐由一個頂點d
和三個態射組成:
p :: d -> a q :: d -> c r :: d -> b
交換性條件是說r
被其餘態射徹底地決定了,能夠從圖中略去。因此咱們就只剩下下面的條件:
g . q = f . p
一個回拉是具備下面這種形狀的泛錐。
像剛剛那樣,若是你只關注集合的話,你就能夠把對象d
當作一些分別來自a
和c
的元素的序對,對它們來講f
在第一個份量上做用的結果與g
在第二個份量上的相等。若是這麼說仍是有些通常,考慮一個特殊的情形:g
是常數函數g _ = 1.23
(假定b
是實數集)。也就是你實際上要解方程:
f x = 1.23
這時,c
的選擇可有可無(只要它不是空集就行),因此咱們能夠取單例集。集合a
,比方說,能夠是個三維向量的集合,而f
計算向量長度。那麼這個回拉就是序對(v, ())
的集合,其中v
是長度是1.23的向量(也就是方程sqrt (x^2+y^2+z^2) = 1.23
的解),而()
是單例集的那個無趣的元素。
但回拉還有更通常的應用,編程中也有這樣的應用。好比,把C++的類想成一個範疇,其中的態射是鏈接子類和父類的箭頭。咱們會把繼承想成一種傳遞性,因此若是C繼承了B而B繼承了A那麼C繼承了A(畢竟,你給建立一個指向C指針就須要一個指向A的指針)。而且,咱們假定C繼承了C本身,因此對每一個類咱們都有了恆等態射。這樣子類就和子類型匹配了。C++也支持多繼承,因此你能夠構造出一個菱形繼承表,B和C繼承了A,第四個類D多繼承了B和C。通常來講,D會有兩個A的拷貝,但這不是咱們所指望的;但你能夠利用虛繼承來只獲得一個A的拷貝。
讓D成爲這個圖表的一個回拉到底是什麼意思?這是說任意一個多繼承B和C的類E也是D的一個子類。這一點在子類型沒有實質意義的C++裏並不能直接表示出(C++編譯器不會推斷這種類關係——它須要「鴨子類型」)。但咱們能夠放下子類型關係,轉而問從E到D的一個轉換是否安全。若是D是B和C的最最瘦的結合方式,沒有新增數據和方法的重載,那麼這種轉換就是安全的。固然,若是B和C的某些方法有命名衝突的話,就沒有這樣的回拉了。
回拉在類型推導裏還有更出色的應用。咱們常常會有求兩個表達式的一致類型的須要。好比,假如編譯器想要推斷下面的函數的類型:
twice f x = f (f x)
編譯器要先給全部變量和子表達式賦初始類型。特別地,它能夠賦成:
f :: t0 x :: t1 f x :: t2 f (f x) :: t3
這時編譯器會推斷出:
twice :: t0 -> t1 -> t3
編譯器也會基於函數應用的規則想出一個約束的集合:
t0 = t1 -> t2 —— 由於f做用在了x上 t0 = t2 -> t3 —— 由於f做用在了(f x)上
這些約束必須在尋找一組類型(或類型變量)時同時(一致地)知足,當把這些未知類型帶入這兩個表達式時,可以獲得同一種類型。一個這樣的代換是:
t1 = t2 = t3 = Int twice :: (Int -> Int) -> Int -> Int
可是呢,明顯,這不是最通常的那個。最通常的代換能夠用一個回拉得到。我不會講那些細節,由於它們超出了這本書的範圍,但你能夠相信這個結果應該是:
twice :: (t -> t) -> t -> t
其中t
是一個自由的類型變量。
像範疇論中全部的構造同樣,極限在反範疇中也有一個對偶的樣子。你把一個錐的全部箭頭的方向都反轉一下,就獲得了一個餘錐,而最泛的那個就叫餘極限。注意這種反轉也會影響因子態射,它如今從那個最泛的餘錐射到其餘餘錐。
餘錐和鏈接兩個頂點的因子態射。
餘極限的典型例子就是餘積,它對應的是咱們以前在積的定義中用到的2範疇生成的圖表。
積和餘積各自以不一樣的方式體現了一對對象的本質。
就像終端對象是一個極限同樣,初始對象是對應由空範疇生成的圖表的餘極限。
回拉的對偶叫作外推。它基於一個由範疇1<-2->3
生成的叫作擴張的圖表。
我早前說過,從函子從不破壞既有的聯繫(態射)這一點上說,它很是接近範疇中的連續映射的想法。一個從範疇C到範疇C'的連續函子F的實際定義還包括了一個保持極限的條件。每一個C中的圖表D
能夠經過簡單地複合兩個函子映射爲C'中的圖表F ∘ D
。F
的連續性條件是說,若是圖表D
有極限Lim D
,那麼圖表F ∘ D
也有一個極限,而且等於F (Lim D)
。
注意,由於函子把態射映爲態射,把複合映爲複合,一個錐的像也老是一個錐。一個交換的三角形夜總會映爲一個交換的三角形(由於函子保持複合)。這一點對因子態射也是成立的:因子態射的像也是一個因子態射。因此每一個函子都是幾乎連續的。可能出錯的是惟一性條件。C'中的因子態射可能不惟一。C'中也可能會有一些在C中不成立的其餘的「更好的錐」。
hom函子就是一個連續函子的例子。回憶一下hom函子C(a, b)
,它在第一個變量上是逆變的,在第二個變量上是協變的。換句話說,它是一個函子:
C^op x C -> Set
看過第一章的朋友應該見過C op,它是範疇C的經過把全部箭頭反向導出的反範疇。
固定它的第二個參數,hom集函子(這時它是個可表預層)會把C的餘極限映爲Set的極限;固定它的第一個參數,它就把極限映爲極限。
在Haskell中,一個hom函子就是將兩個類型映爲一個函數類型的映射,因此它就僅僅是一個參數化的函數類型。當咱們固定第二個參數時,好比取爲String
,咱們獲得了逆變函子:
newtype ToSring a = ToString (a -> String) instance Contravariant ToString where contramap f (ToString g) = ToString (g. f)
連續性是說ToString
應用到一個餘極限上時,好比說餘積Either b c
,就會獲得一個極限;在如今這種狀況下就是兩個函數類型的積:
ToString (Either b c) ~ (b -> String, c -> String)
這沒錯,Either b c
上的任意函數都會用一個帶有兩種情形的case語句來實現,這要提供一對函數。
相似地,當固定hom集的第一個參數時,咱們獲得了熟悉的reader函子。它的連續性是說,好比吧,任何返回一個積的函數都等價於一個函數的積;具體來講就是:
r -> (a, b) ~ (r -> a, r -> b)
我知道你如今在想什麼:你不須要範疇論就能夠想出它們,而且你是對的!儘管如此,這些結果只用一些不涉及到比特和字節、處理器架構、編譯技術或甚至lambda演算的基本原理就能獲得仍是讓我震撼。
若是你對「極限」和「連續性」的命名由來很好奇的話,其實它們是對應的微積分觀念的一個推廣。在微積分中,極限和連續性是用開集來定義的。開集定義了拓撲,而拓撲構成了一個範疇(偏序集)。
Id :: C -> C
的極限是初始對象。[事實上,Id
對應的錐只有一個,它自動就成了最通常的那個,譯者注]感謝Gershom Bazerman檢查個人數學和邏輯,和André van Meulebrouck在編輯上的幫助。
下一篇:自由幺半羣