Algebra of Programming - initial algebra
F-algebra
首先是有一個自函子,F:C <- C,一個對象:A,一個箭頭:A <- FA (先不要在意用<-,而不是->),這個箭頭就是F-algebra啦,對象A為代數的carrier
栗子:自然數加法:algebra (Nat, +),是函子F(FA = A x A & Fh = h x h)(F是個squaring functor)的代數, 也就是A為Nat,代數為:(+) (1, 2) = 3n
F-homomorphism:從一個代數 g: B <- FB 到一個代數 f: A <- FA 的一個箭頭 h: A <- B,使得:h . g = f . Fh
用圖來表示type information:Initial algebra
下面將一個代數如alg: A <- FA 作為一個對象討論,其Identity arrows是F-homomorphism,F-homomorphism 組合上F-homomorphism還是F-homomorphism,所以:
將F-代數作為對象,將F-homomorphism作為箭頭,就形成了一個範疇:Alg(F) (F指代數的函子),那麼其initial object 就是 initial F-algebra (到所有其它代數都有唯一的箭頭(F-homomorphism));並且對於很多函子(包括polynomial functors)都存在這種範疇的initial object(證明:略)下面就可以對初始代數進行定義了:就是範疇Alg(F)的初始對象,記為 a : T<- FT,或(a, T),(在程序中T指Type)利用初始對象的性質,我們發現初始代數a到其它代數如f : A<- FA都有唯一的homomorphism,我們將此homomorphism記為:([ f ]),所以([ f ]) : A <- T,有如下性質:h = ([ f ]) ≡ h . a = f . Fhn
recursively defined datatype
Natural numbers
下面就來看recursively defined datatype,先從最簡單的Nat開始:Nat ::= zero | succ Natn
這其實定義了一個初始代數:[zero, succ] : Nat <- F Nat,函子F為FA = 1 + A & Fh = id1 + h;
(這個函子是polynomial,所以它的Alg(F)範疇存在initial object,也就是存在初始代數)zero : Nat <- 1 是一個常量函數,succ則是Nat <- Nat的函數1 是terminal object,在程序中指只有一個實例的type,如Unit,它的唯一實例為:()id1 是terminal object 的恆等態射(Unit -> Unit)了 ,我們將這個代數畫出來:

這裡是個coproudct結構(sum type),所以對Type,F為 A -> Coproduct[Unit, A],對態射(箭頭):
Fh = id1 + hn = [inl . id1, inr . h] ;{coproduct functor的性質:f + g = [inl . f, inr . g]}n = bimap( inl . id1, inr . h ) : Unit + A => Unit + Bn
所以可以找個coproudct的實例如:Either[A, B] (inl和inr就對應left和right構造子了)來指代該函子
所以函子F就可以是Either[Unit, A],fmap f 就是Either[Unit, A].bimap(left . id1, right . f )所以函子F就是將bifunctor: Either[A, B] fix type A之後的函子Either[Unit, A]所以可以用一個bifunctor去表示該代數(natural numbers):_zero () = Left(zero)n_succ n = Right(succ n)n0 : bimap(_zero, _succ) Left(()) => Left(zero)n1 : bimap(_zero, _succ) Right(zero) => Right(succ(zero))n2 : bimap(_zero, _succ) Right(succ(zero) ) => Right(succ(succ(zero)))n
至此我們可以看出一點結論:函子F(A),是一個二元函子F(A, B) fix 一個type之後的產物,其次這個type declaration 就是為了給initial algebra一個name
([ c, f ])對每一個函子F: C -> C的代數都有[c, f]這樣的形式(如上[zero, succ]),一個常量函數c: A <- 1,一個函數f: A <- A另代數h : A <- FA,有h = [h . inl, h . inr],原因為c = h . inl,f = h . inr,如下圖:
h (0) = cnh (n +1) = f(h n)n
因為homomorphism:h又寫為:([ c, f ])的形式,故h = ([ c, f ]),且觀察上面h函數和Nat的foldn:
foldn(c, f) (0) = c ; 0為zero nfoldn(c, f) n + 1 = f(foldn(c, f) n) ; n + 1 為 succ(n)n
所以得出:h = ([ c, f ]) = foldn(c, f) = cata( [c, f] ) in the datatype Nat, foldn(c, f)就是個homomorphism
所以取不同的c,f函數值,便可以表示Nat的其他homomorphisms如:plus m = foldn (m, succ)nmult m = foldn (0, plus m)nfact = outr . foldn ((0,1), f) where outr(m, n) = n & f(m, n) = (m + l,(m + 1) x n)nfib = outl . foldn ((0,1), f) where outl(m, n) = m & f(m, n) = (n,m + n)n
也可簡記為:plus m = ([m, succ]) , ([ _ ]) 被視為一種新的函數組合方式(組合子)
String下面來看另一種特殊的recursively defined datatype,StringString ::= nil | cons (Char, String)n
它特殊的地方在於String 就是 [Char],它就是一個list Char,為推廣至list A 做準備,先看一下初始代數String
它聲明了代數[nil, cons]: String <- F String 為函子F (FA = 1 + (Char x A),Ff = id + (id x f))的初始代數:
同樣的:h = ([ c, f ]) = foldr(c, f) in the datatype String,並且([ c, f ]) 和 fold操作是一致的
List下面推廣到list A這種帶類型參數的情況,我們用cons-list(listr,還有一種snoc-list叫listl)表示:listr A ::= nil | cons(A, listr A)n
這聲明了 為函子
(
)的初始代數
看類型信息:

這個菱形(將product和coproduct組合在了一起),更清晰的展示了前面String時說的代數與代數數據類型的關係,無非就是將Char換回了類型參數A
這個二元函子可以用Either[Unit, Tuple2[A,B]]來表示而類型構造子listr本身也可被定義為一個函子,稱其為type functor:有二元函子F以及初始代數集合[nil, cons] . (id + (f x id)) = [nil . id, cons . (f x id)]n
故:
listr f = ([ [nil, cons] . (id + (f x id)) ])n= ([ nil . id, cons . (f x id) ]) n= cata([nil, cons] . (id + (f x id)))n= foldr(nil . id, cons . (f x id) )n
通過listr f = ([ nil . id, cons . (f x id) ]) 就可以看出這個listr 就是 fmap,f只會對cons的左邊有影響
Fusion對函子T應用fusion得到:([h]) . Tg = ([ h . F(g, id) ]),也就是說catamorphism和函子T組合得到了單個catamorphism,這非常有用,因為前面Nat的時候列舉過一些操作就是代數的同態函數如對list有:sum = ([zero, plus]),sum: Nat <- listr Nat,則:
sum . listr f = ([zero, plus . (f x id)]) = foldr(zero, plus . (f x id) ),再結合以後會提到的banana-split law 可能是提升性能的關鍵
Lambeks Lemma
下面插播一條引理:



(F為ListFInt[A]),讓我們以這篇文章的知識解構一下前文中的代碼

end
本文主要講了,初始代數與遞歸類型,代數數據類型的關係,新的函數組合形式:([_]),以及對前文Fix不動點的填坑,對《Algebra of Programming》第二章節的核心內容做了一點點補充
以category theory做meta language討論函數式編程,不過本文停留在『是什麼?』的階段,後續會出『怎麼用?』(( `д′) 我褲子都脫了,你就讓我看這個?有生之年系列?)
推薦閱讀:





