Haskell中的Catamorphism和树遍历

时间:2010-12-13 22:54:53

标签: haskell tree-traversal catamorphism

我很不耐烦,期待理解catamorphism related to this SO question:)

我只练习了Real World Haskell教程的开头。所以,也许我现在要求的方式太多了,如果是这样,请告诉我应该学习的概念。

下面,我引用wikipedia code sample for catamorphism

我想知道你对下面的foldTree的看法,这是一种遍历树的方法,与其他SO问题和答案相比,还涉及遍历树n-ary tree traversal。 (独立于二元或不二元,我认为下面的catamorphism可以写,以便管理n-ary树)

我评论了我的理解,如果你能纠正我,并且澄清一些事情,我会很高兴。

{-this is a binary tree definition-}
data Tree a = Leaf a
            | Branch (Tree a) (Tree a)

{-I dont understand the structure between{} 
however it defines two morphisms, leaf and branch 
leaf take an a and returns an r, branch takes two r and returns an r-} 
data TreeAlgebra a r = TreeAlgebra { leaf   :: a      -> r
                                   , branch :: r -> r -> r }

{- foldTree is a morphism that takes: a TreeAlgebra for Tree a with result r, a Tree a
and returns an r -} 
foldTree :: TreeAlgebra a r -> Tree a -> r
foldTree a@(TreeAlgebra {leaf   = f}) (Leaf   x  ) = f x
foldTree a@(TreeAlgebra {branch = g}) (Branch l r) = g (foldTree a l) (foldTree a r)
在这一点上我遇到了很多困难,我似乎猜测了那个态射叶 将适用于任何Leaf 但是为了将这个代码用于实际,foldTree需要被定义的TreeAlgebra, 一个TreeAlgebra,它有一个定义的态射叶,以便做某事? 但在这种情况下,在foldTree代码中我会期望{f = leaf}而不是相反的

非常欢迎您的任何澄清。

2 个答案:

答案 0 :(得分:26)

不完全确定你在问什么。但是,是的,您将TreeAlgebra提供给与您要在树上执行的计算相对应的foldTree。例如,要汇总Int s树中的所有元素,您将使用此代数:

sumAlgebra :: TreeAlgebra Int Int
sumAlgebra = TreeAlgebra { leaf = id
                         , branch = (+) }

这意味着,要获取叶子的总和,请对叶子中的值应用id(不执行任何操作)。要获得分支的总和,请将每个子项的总和加在一起。

事实上,我们可以说(+)代替分支而不是\x y -> sumTree x + sumTree y,这是catamorphism的基本属性。它表示要在某个递归数据结构上计算某个函数f,就可以为其直接子项设置f的值。

Haskell是一种非常独特的语言,因为我们可以抽象地形式化catamorphism的概念。让我们为树中的单个节点创建一个数据类型,并对其子节点进行参数化:

data TreeNode a child
    = Leaf a
    | Branch child child

看看我们在那里做了什么?我们刚刚用我们选择的类型替换了递归子项。这样我们可以在折叠时将子树的总和放在那里。

现在真的是神奇的事情。我将在pseudohaskell中编写这个 - 在真正的Haskell中编写它是可能的,但是我们必须添加一些注释来帮助typechecker,这可能会让人感到困惑。我们采用参数化数据类型的“固定点” - 即构建数据类型T,使T = TreeNode a T。他们将此运算符称为Mu

type Mu f = f (Mu f)

仔细看这里。 Mu的参数不是类型,例如IntFoo -> Bar。它是类型构造函数,如MaybeTreeNode Int - Mu本身的参数接受参数。 (抽象类型构造函数的可能性是使Haskell类型系统在其表达能力中真正脱颖而出的因素之一)。

因此,类型Mu f被定义为使用f并使用Mu f本身填充其类型参数。我将定义一个同义词来减少一些噪音:

type IntNode = TreeNode Int

扩展Mu IntNode,我们得到:

Mu IntNode = IntNode (Mu IntNode)
           = Leaf Int | Branch (Mu IntNode) (Mu IntNode)

您是否了解Mu IntNodeTree Int的对等关系?我们刚刚撕掉了递归结构,然后使用Mu将它重新组合在一起。这为我们提供了一个优势,即我们可以同时讨论所有Mu类型。这为我们提供了定义catamorphism所需的内容。

让我们来定义:

type IntTree = Mu IntNode

我说catamorphism的基本属性是计算一些函数f,它的直接子项的值为f就足够了。让我们调用我们尝试计算的事物的类型r,数据结构nodeIntNode将是一个可能的实例化)。因此,要计算特定节点上的r,我们需要将其子节点替换为r的节点。此计算的类型为node r -> r。因此,一个catamorphism说如果我们有其中一个计算,那么我们可以为整个递归结构计算r(记住递归在这里用Mu明确表示):< / p>

cata :: (node r -> r) -> Mu node -> r

对于我们的示例,这具体如下:

cata :: (IntNode r -> r) -> IntTree -> r

重申一下,如果我们可以为其子节点选择r个节点并计算r,那么我们可以为整个树计算r

为了实际计算这一点,我们需要nodeFunctor - 我们需要能够在节点的子节点上映射任意函数。

fmap :: (a -> b) -> node a -> node b

这可以直接针对IntNode进行。

fmap f (Leaf x) = Leaf x                  -- has no children, so stays the same
fmap f (Branch l r) = Branch (f l) (f r)  -- apply function to each child

现在,终于,我们可以为cata提供一个定义(Functor node约束只是说node有一个合适的fmap) :

cata :: (Functor node) => (node r -> r) -> Mu node -> r
cata f t = f (fmap (cata f) t)

我使用参数名t作为“树”的助记符值。这是一个抽象的,密集的定义,但它确实非常简单。它说:递归执行cata f - 我们在树上进行的计算 - 在每个t的子节点(它们本身是Mu node s)上获得{{1}然后将该结果传递给node r计算f本身的结果。

将此回到开头,您定义的代数本质上是一种定义t函数的方法。实际上,给定node r -> r,我们可以轻松获得折叠函数:

TreeAlgebra

因此,树的变形可以用我们的通用定义如下:

foldFunction :: TreeAlgebra a r -> (TreeNode a r -> r)
foldFunction alg (Leaf a) = leaf alg a
foldFunction alg (Branch l r) = branch alg l r

我没时间了。我知道真的非常抽象,但我希望它至少能给你一个新的观点来帮助你学习。祝你好运!

答案 1 :(得分:4)

我想你是在问一个关于{}的问题。有一个较早的问题,对{}的讨论很好。这些被称为Haskell's record syntax。另一个问题是为什么构造代数。这是一种典型的函数范例,您可以将数据概括为函数。

最着名的例子是Church's construction of the Naturals,其中f = + 1z = 00 = z1 = f z2 = f (f z)3 = f (f (f z)), 等...

您所看到的实际上与应用于树的想法基本相同。工作教会的例子,树将点击。