Haskell 如何实现此折叠功能?
给出了颜色和植物两种数据类型Haskell 如何实现此折叠功能?,haskell,tree,fold,catamorphism,Haskell,Tree,Fold,Catamorphism,给出了颜色和植物两种数据类型 data Color = Red | Pink | White | Blue | Purple | Green | Yellow deriving (Show, Eq) data Plant = Leaf | Blossom Color | Stalk Plant Plant deriving (Show, Eq) 现在,我应该实现以下类型的函数fold\u plant: (x -> x -> x) -> (C
data Color = Red | Pink | White | Blue | Purple | Green | Yellow
deriving (Show, Eq)
data Plant =
Leaf
| Blossom Color
| Stalk Plant Plant
deriving (Show, Eq)
现在,我应该实现以下类型的函数fold\u plant
:
(x -> x -> x) -> (Color -> x) -> x -> Plant -> x
我对折叠函数的理解是,它接受一个列表,每次迭代都会从列表中删除第一个元素,并对该元素进行处理
显然,折叠植物的茎、花、叶是植物的特征
现在我知道,在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
我们看到有茎
部分以及花
部分和叶
部分。我们将在这里命名茎
功能s
和花
功能b
和叶
部分l
。为了简化(和优化)函数,我们将解压这三个参数,然后调用递归方法:
现在的问题当然是如何处理fold\u plant'
。如果我们看到一个Leaf
,我们不需要对值执行任何操作,只需返回我们的Leaf结果l
:
fold_plant' Leaf = l
如果我们发现一个(c)
带有c
a颜色,我们必须使用b
部分执行从c::color
到x
的映射,以获得新值:
fold_plant' (Blossom c) = b c
最后,如果我们有一个茎
,我们将不得不执行递归:我们首先调用左茎上的折叠植物
,然后调用折叠植物
,并构造一个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中,我们更抽象和一般,所以我们的折叠函数可以处理任何类型的结构,我们告诉Haskell折叠的含义
因此,我们可以讨论“使用加法和折叠将数字列表转换为求和值”,或者我们可以讨论“使用函数获取姓名的族谱并将其折叠为列表”,等等。任何时候,我们有这样的想法,将某个东西的结构改变为一个单一的值,或者可能改变为一组不同的结构化值,这就是折叠
在Haskell中,表示这一点的“规范”方法是foldr::Foldable t=>(a->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]
的下一个值,依此类推,直到不再剩下[Int]
,然后返回该函数
它实际上是将函数折叠到数据结构上
这对于列表来说是很好的,它是一条直线,但是你拥有的是一棵树,而不是一个列表。那么它是如何工作的呢?好吧,让我们看看我们如何专门化foldr
,从Int
列表中生成一对最高和最低的数字,而不是foldr:(Int->(Int,Int)->(Int,Int))->(Int,Int)->[Int]->(Int,Int)
。因此,我们取一个函数,该函数取一个Int
和一对,我们将初始对放入其中,以及来自[Int]
的第一个Int
。它返回一个新对,然后我们对[Int]的下一个执行相同的过程
然后我们继续这个过程,直到最后只剩下一对
foldToMinMax=foldr(\newNum(minnum,maxnum)->(minminnum-newNum,max-maxnum-newNum))(maxBound::Int,minBound::Int)
所以现在事情变得更清楚了
但是,你的花之树呢?你需要自己编写一个折叠函数,它包含两个值,其中一个值与初始值和结果值相同,另一个值与树的类型相同,并构建一个结果类型的值以更具描述性的方式,我可能会编写如下内容:foldr::(contentOfCollectionType->resultType->resultType)->resultType->resultType->(collectionWrapper contentOfCollectionType)->resultType
但是你不必在这里使用foldr
,事实上,除非你做了一些奇特的类型类实例化的东西,否则你不能使用它。你完全可以使用简单的递归编写你自己的折叠函数。这就是他们所追求的
如果你想了解复发
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)
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
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