我该如何实现这种折叠功能?

时间:2017-07-04 22:12:16

标签: haskell tree fold catamorphism

给出两种数据类型Color and 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

但从现在开始,我不知道折叠功能如何对植物起作用。

3 个答案:

答案 0 :(得分:5)

如果我们看一下函数签名:

fold_plant :: (x -> x -> x) -> (Color -> x) -> x -> Plant -> x
--            \_____ _____/    \____ _____/    |      |      |
--                  v               v          v      v      v
--                stalk           blossom     leaf  tree  output

我们发现stalk部分以及blossom部分和leaf部分。我们会在此处命名stalk函数sblossom函数b以及leaf部分l。为了简化(和优化)函数,我们将解压缩这三个参数,然后调用递归方法:

fold_plant s b l = fold_plant'
    where fold_plant' = ...

现在问题当然是如何处理fold_plant'。鉴于我们看到Leaf,我们不需要对值执行任何操作,只需返回我们的叶结果l

fold_plant' Leaf = l

如果我们找到(Blossom c) c一种颜色,我们必须执行从c :: Colorx的{​​{1}}部分的映射获得新值:

b

最后,如果我们有一个fold_plant' (Blossom c) = b c ,我们将不得不执行递归:我们首先在左边的句子上调用Stalk,然后我们调用fold_plant'并构造一个{{1两个结果:

fold_plant'

所以我们可以将它们全部放在以下函数中:

s

答案 1 :(得分:1)

折叠是一种在结构中获取一段数据并将其折叠到另一条数据的函数。通常我们这样做是为了将集合“减少”为单个值。这就是为什么如果你看看其他语言,如Lisp,Smalltalk,Ruby,JavaScript等,你会发现这个名为reduce的操作,这是Haskell中可怜的表兄弟。

我说这是可怜的堂兄,因为你对列表的直觉是正确的,但是在Haskell中我们更加抽象和一般,所以我们的折叠函数可以在任何类型的结构上工作,我们已经告诉过它的类型是什么折叠意味着什么对

因此,我们可以谈论“使用fold with fold将数字列表转换为和值”,或者我们可以谈论“使用函数来获取名称的家谱并将其折叠到列表中”,等等等等。任何时候我们都有这样的想法,即将某些东西的结构改为单个值,或者可能是一个不同的结构化值集,这就是折叠。

在Haskell中表示这种情况的“规范”方式是foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b,但是您更容易想到在开头使用“a”列表作为Foldable f => t a类型因为它更容易理解。所以我们有一个专门的foldr :: (a -> b -> b) -> b -> [a] -> b类型。但ab是什么?什么是(a -> b -> b)以及这三个论点做了什么?

让我们专注于Inta的{​​{1}}值:b哇......这让它变得有趣......不是吗?所以foldr :: (Int -> Int -> Int) -> Int -> [Int] -> Int采用两个整数的函数,例如foldr函数,它需要一个(+)值(这是它将用作目标结果的初始值,以及Int值的列表...然后它将从中生成一个Int ...也就是说,它需要Int函数并将其应用于单个Int -> Int -> Int 1}}和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,实际上你不能使用它,除非你做一些花哨的类型类实例化的东西。您可以使用普通递归完全编写自己的折叠函数。这就是他们追求的目标。

如果你想了解递归和折叠等等,而你还不了解这些东西,我推荐我帮助创作的那本书。 http://happylearnhaskelltutorial.com它更详细地解释了它,并有许多明显的例子。如果您了解基础知识,那么在您想要了解递归和折叠的那一点上应该非常快速地进行加速......但是如果您不了解它,那么对您来说理解它将非常有用。基础知识,因为你需要先了解它们才能获得其他东西。

我应该提一下,你的特定折叠也有一个转换功能。这是将foldr转换为Color的事情。您作为折叠函数使用的函数“将x压缩在一起”(即获取两个x值并生成另一个x值,与上面示例中的x非常相似)。它只能在树上工作,因为我们还赋予它这个函数来将(+)变成Color,这有效地将有意义的数据从树中取出并将其放入折叠函数可以的形式使用

这里有一个非常漂亮的模式。

祝你好运!

答案 2 :(得分:0)

折叠是递归问题解决的本质:

    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

请遵循这些类型,并说服自己一切都应该合适。

当然可以避免临时数据定义并且功能定义崩溃,但这是要遵循的通用过程。

(参见递归方案)。