[英]How would I implement this fold function?
給出了兩種數據類型Color和Plant。
data Color = Red | Pink | White | Blue | Purple | Green | Yellow
deriving (Show, Eq)
data Plant =
Leaf
| Blossom Color
| Stalk Plant Plant
deriving (Show, Eq)
現在我應該實現以下類型的函數fold_plant
:
(x -> x -> x) -> (Color -> x) -> x -> Plant -> x
我理解折疊函數的方式是它需要一個列表,並且對於每次迭代,它從列表中刪除第一個元素並對該元素執行某些操作。
顯然fold_plant Stalk Blossom Leaf
是植物的標識。
現在我知道在Haskell中你可以創建這樣的函數:
fold_plant :: (x -> x -> x) -> (Color -> x) -> x -> Plant -> x
fold_plant = do something
但從這里開始,我不知道折疊功能如何對植物起作用。
如果我們看一下函數簽名:
fold_plant :: (x -> x -> x) -> (Color -> x) -> x -> Plant -> x
-- \_____ _____/ \____ _____/ | | |
-- v v v v v
-- stalk blossom leaf tree output
我們看到有stalk
部分以及blossom
部分和leaf
部分。 我們將命名stalk
功能s
和blossom
函數b
這里和leaf
部分l
。 為了簡化(和優化)函數,我們將解包這三個參數,然后調用遞歸方法:
fold_plant s b l = fold_plant'
where fold_plant' = ...
現在的問題當然是如何處理fold_plant'
。 鑒於我們看到一個Leaf
,我們不需要對值執行任何操作,只需返回我們的葉子結果l
:
fold_plant' Leaf = l
如果我們找到一個帶有c
顏色的(Blossom c)
,我們必須執行從c :: Color
到帶有b
部分的x
的映射以獲得新值:
fold_plant' (Blossom c) = b c
最后,如果我們有一個Stalk
我們將不得不執行遞歸:我們首先在左邊的stalk上調用fold_plant'
,然后我們調用fold_plant'
並用兩個結果構造一個s
:
fold_plant' (Stalk lef rig) = s (fold_plant' lef) (fold_plant' rig)
所以我們可以將它們整合到以下函數中:
fold_plant s b l = fold_plant'
where fold_plant' Leaf = l
fold_plant' (Blossom c) = b c
fold_plant' (Stalk lef rig) = s (fold_plant' lef) (fold_plant' rig)
折疊是一種在結構中獲取一段數據並將其折疊到另一條數據的函數。 通常我們這樣做是為了將集合“減少”為單個值。 這就是為什么如果你看看其他語言,如Lisp,Smalltalk,Ruby,JavaScript等,你會發現這個名為reduce
操作,這是Haskell中可憐的表兄弟。
我說這是可憐的堂兄,因為你對列表的直覺是正確的,但在Haskell中我們更抽象和更通用,所以我們的折疊函數可以在任何類型的結構上工作,我們已經告訴過這種結構有什么折疊意味着什么。
因此,我們可以討論“使用with with fold將數字列表轉換為總和值”,或者我們可以討論“使用函數來獲取名稱的族譜並將其折疊到列表中”,等等等等。 任何時候我們都有這樣的想法,即將某些東西的結構改為單個值,或者可能是一組不同的結構化值,這就是折疊。
在Haskell中表示這種情況的“規范”方式是foldr :: Foldable t => (a -> b -> b) -> b -> ta -> b
但它更容易像你想的那樣使用“列表a
“作為Foldable f => ta
類型的開頭因為它更容易理解。 所以我們有一個特殊類型的foldr :: (a -> b -> b) -> b -> [a] -> b
。 但什么是a
和b
? 什么是(a -> b -> b)
這三個論點有什么作用?
讓我們把它專門化為a
和b
Int
值: foldr :: (Int -> Int -> Int) -> Int -> [Int] -> Int
哇...這讓它...有趣不是嗎? 所以foldr
采用兩個整數的函數,比如說, (+)
函數,它需要一個Int
值(這是它將用作目標結果的初始值,以及一個Int
值列表......然后它將從那里產生一個Int
......也就是說,它需要Int -> Int -> Int
函數並將其應用於單個Int
和第一個[Int]
,然后將該函數應用於該結果和[Int]
的下一個值,依此類推,直到不再有[Int]
為止......那就是它返回的內容。
它完全折疊了數據結構上的功能。
對於列表而言,這一切都很好,這是一條直線,但你擁有的是樹,而不是列表。 那它怎么在那里工作? 那么,讓我們看看我們如何專門使用foldr
來從Int
列表中生成一對最高和最低數字? foldr :: (Int -> (Int, Int) -> (Int, Int)) -> (Int, Int) -> [Int] -> (Int, Int)
。 所以我們采用一個帶有Int
和一對的函數,然后我們將初始對與我們的[Int]
的第一個Int
放在一起。 這會返回一個新的對,然后我們為[Int]
的下一個做同樣的過程,然后我們繼續這個過程,直到我們留下的所有東西都是最后一對。
foldToMinMax = foldr (\\newNum (minnum,maxnum) -> (min minnum newNum, max maxnum newNum)) (maxBound :: Int, minBound :: Int)
所以,現在事情變得有點清晰。
你有這棵花怎么樣呢? 好吧,你需要自己編寫一個折疊函數,它將獲取兩個值,其中一個值與初始值和結果值相同,另一個將是樹的類型,並構建一個值結果類型。 如果我使用偽代碼以更具描述性的方式編寫類型,我可能會寫類似: foldr :: (contentOfCollectionType -> resultType -> resultType) -> resultType -> (collectionWrapper contentOfCollectionType) -> resultType
但是你不必在這里使用foldr
,事實上你不能使用它,除非你做一些花哨的類型類實例化的東西。 您可以使用普通遞歸完全編寫自己的折疊函數。 這就是他們追求的目標。
如果你想了解遞歸和折疊以及諸如此類的東西,而你還不了解這些東西,我推薦我幫助創作的那本書。 http://happylearnhaskelltutorial.com它更詳細地解釋了它,並有許多清晰的例子。 如果您了解基礎知識,那么在您想要了解遞歸和折疊的那一點上應該非常快速地進行加速......但是如果您不了解它,那么對您來說理解它將非常有用。基礎知識,因為你需要先了解它們才能獲得其他東西。
我應該提一下,你的特定折疊也有一個轉換功能。 這是將Color
轉換為x
。 您作為折疊函數使用的函數“將x壓縮在一起”(即獲取兩個x
值並生成另一個x
值,與上面的示例中的(+)
非常相似)。 它只能在樹上工作,因為我們還提供了這個函數來將Color
轉換為x
,它有效地將有意義的數據從樹中取出並將其放入折疊函數可以使用的形式中。
這里有一個非常漂亮的模式。
祝好運!
折疊是遞歸問題解決的本質:
data Plant = data Result r =
Leaf RLeaf
| Blossom Color | RBlossom Color
| Stalk Plant Plant | RStalk r r
-- recursive data -- non-recursive data: `r`, not `Result r`!
遞歸問題解決是關於以簡單的方式組合遞歸處理原始結構的組成部分的結果:
-- single-step reduction semantics:
-- reduction_step :: ..... -> Result r -> r
reduction_step :: (r -> r -> r) -> (Color -> r) -> r -> Result r -> r
reduction_step s b l RLeaf = l
reduction_step s b l (RBlosom c) = b c
reduction_step s b l (RStalk x y) = s x y
但是為了達到這一點,我們需要進入原始結構的組成部分,這些部分與整個結構具有相同的性質,因此我們尋求創建的fold_plant
程序可以應用於它們,就像已經寫好了一樣( 遞歸) ! ):
recurse_into :: (r -> r -> r) -> (Color -> r) -> r -> Plant -> Result r
recurse_into s b l Leaf = RLeaf
recurse_into s b l (Blossom c) = RBlossom c
recurse_into s b l (Stalk lt rt) = RStalk (fold_plant s b l lt) (fold_plant s b l rt)
所以,最后,我們的折疊只是兩者的組合,
fold_plant :: (r -> r -> r) -> (Color -> r) -> r -> Plant -> r
fold_plant s b l plant = reduction_step s b l -- Result r -> r
(recurse_into s b l plant) -- Plant -> Result r
遵循這些類型並說服自己,所有東西都應該合適。
當然可以避免臨時數據定義並且函數定義崩潰,但這是要遵循的一般過程。
(參見遞歸方案 )。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.