Haskell中的亚同态与树遍历
我不耐烦了,期待着理解变形:) 我只练习了真实世界Haskell教程的开头部分。所以,也许我现在要求的太多了,如果是这样的话,就告诉我应该学习的概念 下面,我引述 我想知道你对下面的foldTree的看法,这是一种遍历树的方法,与其他的问答相比,也是关于遍历树的。(独立于是否为二进制,我认为可以编写下面的反同构,以便管理n元树) 我把我理解的东西写进评论,如果你能纠正我,澄清一些事情,我会很高兴Haskell中的亚同态与树遍历,haskell,tree-traversal,catamorphism,Haskell,Tree Traversal,Catamorphism,我不耐烦了,期待着理解变形:) 我只练习了真实世界Haskell教程的开头部分。所以,也许我现在要求的太多了,如果是这样的话,就告诉我应该学习的概念 下面,我引述 我想知道你对下面的foldTree的看法,这是一种遍历树的方法,与其他的问答相比,也是关于遍历树的。(独立于是否为二进制,我认为可以编写下面的反同构,以便管理n元树) 我把我理解的东西写进评论,如果你能纠正我,澄清一些事情,我会很高兴 {-this is a binary tree definition-} data Tree a =
{-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,
一种树形文胸,它有一个定义的态射叶以便做某事?但是在这种情况下,在foldTree代码中,我期望{f=leaf},而不是相反
非常欢迎您的任何澄清。不太清楚您在问什么。但是,是的,您将一个
TreeAlgebra
馈送到foldTree
中,该树对应于您要在树上执行的计算。例如,要对Int
s树中的所有元素求和,可以使用以下代数:
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)
仔细看这里。Mu
的参数不是一种类型,如Int
或Foo->Bar
。它是一个类型构造函数,如可能
或TreeNode Int
——Mu的参数本身带有一个参数。(抽象类型构造函数的可能性是使Haskell的类型系统在其表达能力方面真正脱颖而出的原因之一)
因此类型muf
被定义为取f
并用muf
本身填充其类型参数。我将定义一个同义词来减少一些噪音:
type IntNode = TreeNode Int
扩展Mu IntNode
,我们得到:
Mu IntNode = IntNode (Mu IntNode)
= Leaf Int | Branch (Mu IntNode) (Mu IntNode)
您是否看到Mu IntNode
如何等效于您的树Int
?我们刚刚将递归结构拆开,然后使用Mu
将其重新组合起来。这给了我们一个优势,我们可以同时讨论所有Mu
类型。这就给了我们定义一个亚同态所需要的东西
让我们定义一下:
type IntTree = Mu IntNode
我说过,反同构的基本性质是,为了计算某个函数f
,它的直接子函数的f
值就足够了。让我们调用我们试图计算的对象的类型r
,数据结构node
(IntNode
可能是这个的一个实例)。因此,要在特定节点上计算r
,我们需要将节点及其子节点替换为它们的r
s。此计算具有类型节点r->r
。因此,一个反同构表示,如果我们有其中一个计算,那么我们可以计算整个递归结构的r
(记住递归在这里用Mu
明确表示):
为我们的示例制作此混凝土,如下所示:
cata :: (IntNode r -> r) -> IntTree -> r
重新启动时,如果我们可以将具有r
s的节点作为其子节点并计算r
,那么我们可以为整个树计算r
为了实际计算它,我们需要节点
成为函子
——也就是说,我们需要能够将任意函数映射到节点的子节点上
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
的定义(函子节点
约束只是说节点
有一个合适的fmap
):
我使用参数名t
作为“tree”的助记符值。这是一个抽象、密集的定义,但实际上非常简单。它说:递归地对t
的每个子节点(它们本身就是Mu节点
s)执行cata f
——我们在树上进行的计算,以获得节点r
,然后将该结果传递给f
计算t
本身的结果
从一开始,您定义的代数本质上是定义节点r->r
函数的一种方法。实际上,给定一个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)
我没时间了。我知道这很快就变得抽象了,但我跳了
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)