範疇category:組合的本質

banq發表於2014-11-07


之前我在分解和組合的抽象方法一文中談了分解decomposition和組合composition具體特點,範疇理論大師Bartosz Milewski最近正好寫了這篇Category: The Essence of Composition,從範疇角度挖掘了分解組合和樹形結構以及構造定律的本質,並解釋了函式程式設計FP的一些原理,如果你對這方面已經有所思考,讓我們一起深入細節吧。下面是大概翻譯。

一個範疇category 其實是一個非常簡單的概念,一個範疇由多個物件和它們之間的箭頭組成,這就是為什麼範疇如此容易表達的原因,一個物件能用一個圓和一個點畫出來,一個箭頭就是一個箭頭,如下圖所示。範疇category:組合的本質

但是範疇的基本本質是組合,當然你也可以說,組合的本質是範疇,如果你有一個箭頭從物件A到物件B,又有一個箭頭從物件B指向物件C,那麼這兩個箭頭的組合結果是,肯定有一個箭頭從物件A指向物件C。

箭頭作為函式
這就是抽象嗎?可能你有點失望,讓我們講些核心的,想想箭頭,也稱為態射morphisms,它用來作為函式,如果你有一個函式f,其將型別A作為輸入引數,返回輸出的是型別B,如果還有另外一個函式g,它是將型別B作為輸入引數,返回輸出的是型別C,你就能透過將f的結果傳給g組合它們,你也就定義了一個新的函式,它是將型別A作為輸入引數,返回輸出的是型別C。

在數學中,這樣的組合是在函式之間使用小圓圈表達,如:g∘f,注意從右到左是組合的順序,如果你還有寫疑惑,如果你熟悉Unix/linux,其管道命令如下:

lsof | grep Chrome

或者F中的>>,它們都是表達從左到右,但是在數學和Haskell函式組合中,組合是從右到左,你可以將 g∘f 讀成, “g after f.”(g在f後面)

我們可以使用C程式碼來更明確表達,我們有一個函式f,它將型別A作為輸入引數,返回輸出的是型別B的值。

B f(A a);

另外一個函式:

C g(B b);

它們的組合是:

C g_after_f(A a)
{
    return g(f(a));
}
<p class="indent">


這裡你看到從右到左的組合: g(f(a)),這是在C語言中。

我很希望告訴你在C++標準庫中有一個模板能夠將兩個函式組合在一起然後返回,但是沒有,而在Haskell中,我們可以這樣表達一個A到B的函式:
f :: A -> B

類似有:
g :: B -> C

它們的組合是:
g . f

一旦你看到Haskell如此簡單表達函式組合,而C++如此無力,其實Haskell可以直接讓你用Unicode字元表達組合如下:
g ∘ f

甚至可以使用雙冒號和箭頭表達如下:
f ∷ A → B

這是Haskell 的第一課,雙冒號表達的是:有某個型別,一個函式的型別是使用在兩個型別之間插入一個箭頭來表達,你能這樣透過一個句號點來表達兩個函式的組合。


組合的特性

在範疇論中,組合必須滿足兩個特性:

1. 組合是關聯的associative
(banq:組合是一種關係,如同金木水火土是一種組合關係一樣),如果你有三個態射(箭頭),比如f, g 和h,那就能夠組合(它們的物件必須是端對端end-to-end,樹葉?),你不必使用括號組合它們,數學符合表達成:
h∘(g∘f) = (h∘g)∘f = h∘g∘f

使用Haskell虛擬碼如下:

f :: A -> B
g :: B -> C
h :: C -> D
h . (g . f) == (h . g) . f == h . g . f
<p class="indent">


(虛擬碼的意思是等於號並不是為函式定義的)

關聯性是在處理函式時相當顯目,也許在其他範疇並沒有如此明顯。

2.對於每個物件A有一個箭頭代表組合單元,這個箭頭是從物件迴圈指向自己,那就意味著是一個組合的基本單元,從A開始在A自身終結的箭頭,相應地其返回同樣的箭頭,物件A的箭頭單元稱為idA (banq注:由於字元原因,後面的A要矮一半),這表達A的標識identity ,在數學符號中如果f是從A到B,那麼:
f∘idA = f

idB∘f = f

當處理函式時,標識箭頭被實現為標識函式,該函式返回的是自己的輸入引數,這個實現對於每個型別都是相同的,那就意味這個函式是通用的多型性。在C++我們能將其作為一個模板定義:
template<class T> T id(T x) { return x; }

當然,在C++中沒有這麼簡單,因為你不只是傳遞它,而且還要涉及如何傳遞,是按引用傳遞 按值傳遞 等等。

在Haskell中,標識函式是標準庫的一部分,稱為Prelude,下面是它的定義表達:
id :: a -> a
id x = x

正如你看到,在Haskell多型函式是出奇簡單,你只需要使用一個型別變數替代型別,核心型別的名稱總是以大寫字母開始,型別變數的名稱總是以小寫字母開始,這裡a代表所有型別。

Haskell函式定義是由函式的名稱,後面跟著一個形式引數,這裡只有一個x,函式體跟在等號後面,這種簡潔常常初學者震驚,但你很快就會看到它意義非凡,函式定義和函式呼叫是函式程式設計中的麵包和黃油,這樣它們的語法是需要簡單到最小,不僅沒有包圍引數的括號,引數之間也沒有逗號間隔開。

函式體總是一個表示式,在函式中沒有任何statements,函式的結果也是一個表示式,這裡只是x。

這是Haskell的第二課。

標識情況能用偽Haskell程式碼寫如下:
f . id == f
id . f == f

這裡你也許會有一個問題,為什麼人們總是要用到標識函式,一個什麼都不做的函式?那麼,為什麼人們使用數字零呢?零是代表什麼也沒有,古羅馬的數字系統是沒有零的,他們能夠建立很棒的道路和渡槽,一些保留至今。

自然數字零或id實際非常有用,這是當它們使用在符號變數中時,那就是為什麼羅馬人不擅長代數的原因,相反,阿拉伯人和波斯人擅長,他們非常熟悉零的概念,一個標識函式可以非常便利地作為引數或返回引數,或一個高階函式,高階函式其實就是函式可能的符號實現,它們是函式的代數。

總結一下,一個範疇是由物件和箭頭組成,箭頭能夠組合 組合是關聯的,每個物件都有一個標識箭頭,作為組合的基本單元使用。

組合是程式設計的本質
(banq:下面關於組合的意義乾貨來了)

函式程式設計者們有一個奇特的目標性問題,他們總是詢問類似零的問題, 實際中,當我們設計一個互動程式,他們會問:什麼是互動?什麼時候實現Conway的人生遊戲?他們可能會思考人生的意義,以這種正規化,我會問什麼是程式設計?最基本概念,程式設計是告訴電腦做什麼. 將記憶體地址的x的內容加入到暫存器EAX的內容,但是當我們彙編程式設計時,我們發給計算機的指令是有意義的表示式,我們解決了一個不平凡的問題(如果平凡我們就不需要計算機幫助了),那麼我們是怎麼解決問題?

我們分解大的問題為小的問題,如果小的問題還是很大,我們繼續分解它,最後我們編寫程式碼來解決這些分解後的小問題,那麼程式設計的本質就來了:我們是組合這些程式碼片段來為一個大問題建立解決方案。如果我們不能將那些碎片程式碼組合起來,分解就沒有必要 (banq注:如果拆了不能裝起來,拆就是破壞了)

分解和組合的並不影響電腦,但是它受限於人的智力,我們的大腦在某個時間只能處理一些概念,最常見的心理學論文之一:The Magical Number Seven, Plus or Minus Two指出我們只能保持 7 ± 2 個資訊片段,我們對人類短期記憶的理解已經改變,但是我們確認它是有限的,底線是我們不能處理一鍋湯一樣的程式碼,我們需要結構不是因為良好結構的程式碼看上去讓人高興,而是因為我們的大腦不能有效處理(非結構的資料),我們經常描述一段程式碼如何優雅和美麗,但是我們真實意思是它們容易被人類智力處理,優雅程式碼代表的數量正好符合我們大腦處理大小,符合我們精神系統能夠消化的食物量大小。

那麼程式組合的數量是多少正好呢?表面數量總是小於它們的體積(幾何學中表面積的增長總是慢於體積的增長),表面積是我們需要組合的資訊片段,而體積是我們需要實現的資訊,一旦一段資訊被實現,我們就會忘記實現細節,而關注它是如何和其他片段互動上 (banq注:符合老子道德經中的無以為用),在物件導向程式設計中,表面是物件的類,或它的抽象介面,在函式程式設計中,它是函式的宣告。

範疇理論總是鼓勵我們從物件內部細節中轉移開來(banq注:我之前帖子中的一張桌子理論,無才能用),在範疇理論中一個物件是一個抽象模糊的實體,你所有需要知道的只是它如何和其他物件互動(關係),它是怎麼使用箭頭和其他物件連線的,這就是為什麼網際網路搜尋引擎(Google)能夠透過分析有多少其他網站連結指向你的這個網站,根據這些鏈入和鏈出的連結數量對你的網站進行排名(PageRank)。

在物件導向程式設計中,一個理想化的物件不僅是透過其抽象介面可見的(純表面,無體積),還有物件的方法method,因為方法代表箭頭(如果方法裡呼叫其他物件會產生對其他物件的依賴,箭頭代表物件之間的組合關係),這一刻你得進入物件的內部才能搞清楚它和其他物件如何組合,但是你就失去了程式設計的優點。


[該貼被admin於2014-11-08 08:57修改過]

相關文章