Haskell中的foldl和foldr的聯繫?
有同學看書,提到Haskell中foldl和foldr如何互相實現,思考了良久,得到了一個答案,但總覺得似懂非懂(第一題還可以一步步推導出來,但第二題基本是靠猜的)。
foldLeft f x ys = xx x
where
h y s = x -&> s (f x y)
xx = foldr h id ys
這兩個答案如此相似是源於某種巧合么...抑或是有某種內在的統一的規律?像這樣用fold來構造高階函數的方法,是不是和Combinator或者Continuation Passing Style有聯繫?由於解題過程比較艱難苦悶,總覺得自己對一些基礎的東西理解不太透徹。若想提高自己思考和解決這類問題方面的效率,不知有哪些資料可以參考。希望各位菊苣們指點一二~
foldRight f x ys = xx x
where
h s y = x -&> s (f y x)
xx = foldl h id ys
謝邀!15年12月18日更新:fold這個函數背後其實能牽扯出很多很本質的東西。fold函數從List這個遞歸數據類型reduce得到一個值,和F-alg的不動點有關係,因此具有泛性質(universal property),如下所示:
g [] = v
&<===&> g = fold f v
g (x:xs) = f x (g xs)
h . fold g w = fold f z
h w = z
h (g x y) = f x (h y)
-- Definition of foldl
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
-- Since parameter f stay constant during recursion, so define a local
-- function g that performs the recursion.
foldl f z xs = g xs z
where
g [] z = z
g (x:xs) z = g xs (f z x)
-- Rewrite g by partial apply
foldl f z xs = g xs z
where
g [] = z -&> z
g (x:xs) = z -&> g xs (f z x)
-- So function g return the function that has one parameter.
foldl f z xs = g xs z
where
g [] = id
g (x:xs) = z -&> g xs (f z x)
-- According by universal property of fold, we have two equation
g [] = id
g (x:xs) = k x (g xs)
-- By definition of function g, we get
z -&> g xs (f z x) = k x (g xs)
-- By apply the argument z, we get
g xs (f z x) = k x (g xs) z
-- By replace with g = g xs, we get
g (f z x) = k x g z
-- By expand k, and recursion capture in g, we get
k = x g -&> z -&> g (f z x)
k = x g -&> v -&> g (f v x)
-- According universal property of fold, we have follow equation
foldl f z xs = fold (x g -&> v -&> g (f v x)) id xs z
foldl f z xs = fold step id xs z
where step x g = v -&> g (f v x)
上面右邊式子中的函數v -&> g (f v x)是fold函數的累加值,通過遍歷List將多個函數組合起來(通過函數的apply)得到一個大的函數,其初始值是id函數。這是一個高階函數的概念,xs這個List被變換為函數z -&> g (f v x)組成的List。
更具體的可以參考論文A tutorial on the universality and expressiveness of fold -- http://www.cs.nott.ac.uk/~pszgmh/fold.pdf。
另外,Jeremy Gibbons在2014年寫了一篇使用fold來演算DSL的論文,非常精彩,具體參考Folding Domain-Specific Languages:Deep and Shallow Embeddings -- http://www.cs.ox.ac.uk/jeremy.gibbons/publications/embedding.pdf。
------ 我是分割線 ---------
一圖勝千言。
上面兩張圖形象的揭示了foldr和foldl的意義,這兩個函數之間的聯繫也就很清晰了。正如 @祖與占提到的,fold函數在範疇論中對應的就是catamorphism,其作用就是將遞歸的數據結構reduce為一個值。我們將foldr稍作變形,可以看的更清楚些。type ListAlgebra a b = (b, a -&> b -&> b)
fold :: ListAlgebra a b -&> [a] -&> b
fold (fzero, fcons) [] = fzero
fold (fzero, fcons) (x:xs) = x `fcons` fold (fzero, fcons) xs
在ghci中測試一下
Prelude&> fold (0, (+)) [1..3]
6
和foldr的功能一樣,這個實現和上面圖的描述就更接近了。
再扯遠一點,foldr和unfoldr是一對具有相反作用的函數,unfoldr在範疇論中對應的是anamorphism,其作用是通過給定的seed和遞推關係構造出一個遞歸的數據結構。unfoldr :: (b -&> Maybe (a, b)) -&> b -&> [a]
再追究的深入一點,就會和free object有聯繫了,比如free monad這些東西。
參考鏈接:CatamorphismAnamorphismhttp://www.cs.nott.ac.uk/~pszgmh/fold.pdfhttp://www.cs.ox.ac.uk/jeremy.gibbons/publications/embedding.pdf謝邀, 具體的答案應該是xxmorphism 之類的東西, 但是我不會, 我來吹下牛逼就完.--
數學家能找到定理之間的相似之處,優秀的數學家能看到證明之間的相似之處,卓越的數學家能察覺到數學分支之間的相似之處。最後,究級的數學家能俯瞰這些相似之處之間的相似之處。
以下為本菜雞的&心路&腦洞大開歷程:
... Give a property that op should satisfy to guarantee that fold-right and fold-left will produce the same values for any sequence.
Cool, 這比玩OJ等AC有意思多了, 當時學了一點抽象代數的皮毛, 馬上意識到這貨要滿足結合律, 它是一個monoid ! 再之後用fold實現了一遍map, filter之後覺得這種遍曆數據結構的方式有共通之處.
在各種Google, 翻論文之後知道研究這些遍歷模式的領域叫Recursion Schemes, 還有涉及Program Calculation的一些東西, 貌似這領域已經沒多少人研究
之後發現牽涉貓論, 智商不夠放棄研究, 完.--ref: foldl.comfoldr.com可以關注這些人的工作(時間序):
Lambert Meertens, Erik Meijer, Graham Hutton, Jeremy Gibbons, Nicolas Wu(這哥們略像彥祖)Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire 奠基之作A tutorial on the universality and expressiveness of fold 寫實際代碼推一下這篇就好Bird–Meertens formalism xxmorphismSorting MorphismsPatterns in Functional Programming近來也有不少關於Recursion Schemes的教程, 不一一列舉在列表有限的情況下, 可以簡單的有如下關係
```haskell
foldr f z xs = foldl (flip f) z (reverse xs)
```
時隔許久,參考了菊苣們推薦的兩份資料:
A tutorial on the universality and expressiveness of fold 【1】
Introduction to Functional Programming, 1ed 【2】
然後自己來補充一下。
- 對於用foldr表示foldl,文獻【1】中主要解釋了foldr的universal性質和fusion法則:即List的遞歸本質,常見的基於List的遞歸函數包括foldl都可以用foldr表達。具體內容parker liu已經作答,不再贅述。
- 反之,對於用foldl表示foldr,文獻【2】中第3.5.1部分介紹了foldl和foldr的對偶定則,其中第三條藉助reverse表述為:
foldr p b as = foldl q b (reverse as)
where q b a = p a b
- 首先利用foldl的定義證明關於foldl的一個lemma:
q (foldl q b as) a == foldl q b (as ++ [a])
用歸納法:
BASE -- let as = []
LHS == q (foldl q b []) a0
== q b a0
RHS == foldl q b ([] ++ [a0])
== foldl q b (a0:[])
== foldl q (q b a0) []
== q b a0
== LHS
OK
SUPP -- let as = as
LHS == q (fold q b as) a0
RHS == foldl q b (as ++ [a0])
== LHS
INDU -- let as = (a:as)
LHS == q (foldl q b (a:as)) a0
== q (foldl q (q b a) as) a0
-- use SUPPOSE with b = (q b a)
== foldl q (q b a) (as ++ [a0])
RHS == foldl q b ((a:as) ++ [a0])
== foldl q (q b a) (as ++ [a0])
== LHS
OK
foldr p b as = foldl q b (reverse as)
where q b a = p a b
同樣使用歸納法:
BASE -- let as = []
LHS == foldr p b []
== []
RHS == foldl q b (reverse [])
== foldl q b []
== []
== LHS
OK
SUPP -- let as = as
LHS == foldr p b as
RHS == foldl q b (reverse as)
== LHS
INDU -- let as = (a:as)
LHS == foldr p b (a:as)
== p a (foldr p b as)
-- use SUPP
== p a (foldl q b (reverse as))
-- use p a b = q b a
== q (foldl q b (reverse as)) a
RHS == foldl q b (reverse (a:as))
== foldl q b (reverse as ++ [a])
-- use lemma
== q (foldl q b (reverse as)) a
== LHS
OK
由於reverse本身可以用foldl表示,即
reverse xs = foldl (xs x -&> (x:xs)) xs
就當作解決了吧(霧
值得注意的是,【1】中提及了用foldl表示foldr嚴格意義上是不可行的,這種表達無法保留laziness,個中深意仍待領悟…
一個是左遞歸,一個是右遞歸
推薦閱讀:
※紅塵里的Haskell(之一)——Haskell工具鏈科普
※用哪些編程語言寫出的代碼,讀著能感受到美?
※函數式編程的早期歷史
※程序語言設計理論有哪些優秀的在線課程?