16

我很不耐烦,期待了解与这个 SO 问题相关的catamorphism :)

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

下面,我引用了catamorphism 的维基百科代码示例

我想知道您对下面 foldTree 的看法,这是一种遍历 Tree 的方式,与其他 SO 问题和答案相比,还涉及遍历 Tree n-ary tree traversal。(与是否为二元无关,我认为可以编写下面的变态以管理 n 叉树)

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

{-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)

在这一点上我遇到了很多困难,我似乎猜测态射叶将应用于任何叶但是为了真正使用这段代码,需要为 foldTree 提供一个定义的 TreeAlgebra,一个具有定义的态射叶的 TreeAlgebra从而做点什么?
但在这种情况下,在 foldTree 代码中,我希望 {f = leaf} 而不是相反

非常欢迎您的任何澄清。

4

2 回答 2

28

不完全确定你在问什么。但是,是的,您输入一个TreeAlgebrafoldTree您要在树上执行的计算相对应的值。例如,要对Ints 树中的所有元素求和,您可以使用以下代数:

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

这意味着,要获得叶子的总和,对叶子id中的值应用(什么都不做)。要获得一个分支的总和,请将每个孩子的总和相加

我们可以说(+)分支而不是说,这一事实\x y -> sumTree x + sumTree y是变质的基本属性。它说,要在某个递归数据结构上计算某个函数,只需为其直接子代f具有 的值即可。f

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

data TreeNode a child
    = Leaf a
    | Branch child child

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

现在来看看真正神奇的东西。我打算用 pseudohaskell 写这个——用真正的 Haskell 写是可能的,但是我们必须添加一些注释来帮助类型检查器,这可能有点令人困惑。我们采用参数化数据类型的“不动点”——即构造一个数据类型T,使得T = TreeNode a T. 他们称之为运营商Mu

type Mu f = f (Mu f)

仔细看这里。to 的参数Mu不是类型,例如Intor Foo -> Bar。它是一个类型构造函数,例如Maybeor TreeNode 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 IntNode等同于你的Tree Int?我们刚刚将递归结构拆开,然后Mu又将其重新组合在一起。这给了我们一个优势,我们可以一次讨论所有Mu类型。这为我们提供了定义变态所需要的东西。

让我们定义:

type IntTree = Mu IntNode

我说变态的基本属性是计算某个函数,它的直系子f级的值就足够了。f让我们称我们正在尝试计算的事物的类型r和数据结构nodeIntNode可能是它的实例化)。因此,要r在特定节点上进行计算,我们需要将该节点及其子节点替换为它们r的 s。这个计算有类型node r -> r。所以变态说如果我们有这些计算之一,那么我们可以计算r整个递归结构(记住递归在这里明确表示Mu):

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

为我们的示例具体化,如下所示:

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

重申一下,如果我们可以将一个带有rs 的节点作为它的子节点并计算一个r,那么我们就可r以为整个树计算一个 。

为了实际计算这一点,我们需要node成为一个Functor——也就是说,我们需要能够将任意函数映射到节点的子节点上。

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

现在,最后,我们可以给出一个定义cataFunctor 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)上得到 a node r,然后传递那个结果来f为自己计算结果t

回到开头,您定义的代数本质上是定义该node r -> r函数的一种方式。事实上,给定 a TreeAlgebra,我们可以很容易地得到 fold 函数:

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

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

type Tree a = Mu (TreeNode a)

treeCata :: TreeAlgebra a r -> (Tree a -> r)
treeCata alg = cata (foldFunction alg)

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

于 2010-12-14T01:32:30.863 回答
4

我想你是在问一个关于 {} 的问题。有一个较早的问题很好地讨论了 {}。这些被称为Haskell 的记录语法。另一个问题是为什么要构造代数。这是一个典型的函数范例,您可以将数据概括为函数。

最著名的例子是Church 对 Naturals 的构造,其中f = + 1and z = 0, 0 = z, 1 = f z, 2 = f (f z), 3 = f (f (f z)), 等等...

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

于 2010-12-15T05:31:10.500 回答